agentscope-runtime 0.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.
Files changed (131) hide show
  1. agentscope_runtime/__init__.py +4 -0
  2. agentscope_runtime/engine/__init__.py +9 -0
  3. agentscope_runtime/engine/agents/__init__.py +2 -0
  4. agentscope_runtime/engine/agents/agentscope_agent/__init__.py +6 -0
  5. agentscope_runtime/engine/agents/agentscope_agent/agent.py +342 -0
  6. agentscope_runtime/engine/agents/agentscope_agent/hooks.py +156 -0
  7. agentscope_runtime/engine/agents/agno_agent.py +220 -0
  8. agentscope_runtime/engine/agents/base_agent.py +29 -0
  9. agentscope_runtime/engine/agents/langgraph_agent.py +59 -0
  10. agentscope_runtime/engine/agents/llm_agent.py +51 -0
  11. agentscope_runtime/engine/deployers/__init__.py +3 -0
  12. agentscope_runtime/engine/deployers/adapter/__init__.py +0 -0
  13. agentscope_runtime/engine/deployers/adapter/a2a/__init__.py +2 -0
  14. agentscope_runtime/engine/deployers/adapter/a2a/a2a_adapter_utils.py +425 -0
  15. agentscope_runtime/engine/deployers/adapter/a2a/a2a_agent_adapter.py +69 -0
  16. agentscope_runtime/engine/deployers/adapter/a2a/a2a_protocol_adapter.py +60 -0
  17. agentscope_runtime/engine/deployers/adapter/protocol_adapter.py +24 -0
  18. agentscope_runtime/engine/deployers/base.py +17 -0
  19. agentscope_runtime/engine/deployers/local_deployer.py +586 -0
  20. agentscope_runtime/engine/helpers/helper.py +127 -0
  21. agentscope_runtime/engine/llms/__init__.py +3 -0
  22. agentscope_runtime/engine/llms/base_llm.py +60 -0
  23. agentscope_runtime/engine/llms/qwen_llm.py +47 -0
  24. agentscope_runtime/engine/misc/__init__.py +0 -0
  25. agentscope_runtime/engine/runner.py +186 -0
  26. agentscope_runtime/engine/schemas/__init__.py +0 -0
  27. agentscope_runtime/engine/schemas/agent_schemas.py +551 -0
  28. agentscope_runtime/engine/schemas/context.py +54 -0
  29. agentscope_runtime/engine/services/__init__.py +9 -0
  30. agentscope_runtime/engine/services/base.py +77 -0
  31. agentscope_runtime/engine/services/context_manager.py +129 -0
  32. agentscope_runtime/engine/services/environment_manager.py +50 -0
  33. agentscope_runtime/engine/services/manager.py +174 -0
  34. agentscope_runtime/engine/services/memory_service.py +270 -0
  35. agentscope_runtime/engine/services/sandbox_service.py +198 -0
  36. agentscope_runtime/engine/services/session_history_service.py +256 -0
  37. agentscope_runtime/engine/tracing/__init__.py +40 -0
  38. agentscope_runtime/engine/tracing/base.py +309 -0
  39. agentscope_runtime/engine/tracing/local_logging_handler.py +356 -0
  40. agentscope_runtime/engine/tracing/tracing_metric.py +69 -0
  41. agentscope_runtime/engine/tracing/wrapper.py +321 -0
  42. agentscope_runtime/sandbox/__init__.py +14 -0
  43. agentscope_runtime/sandbox/box/__init__.py +0 -0
  44. agentscope_runtime/sandbox/box/base/__init__.py +0 -0
  45. agentscope_runtime/sandbox/box/base/base_sandbox.py +37 -0
  46. agentscope_runtime/sandbox/box/base/box/__init__.py +0 -0
  47. agentscope_runtime/sandbox/box/browser/__init__.py +0 -0
  48. agentscope_runtime/sandbox/box/browser/box/__init__.py +0 -0
  49. agentscope_runtime/sandbox/box/browser/browser_sandbox.py +176 -0
  50. agentscope_runtime/sandbox/box/dummy/__init__.py +0 -0
  51. agentscope_runtime/sandbox/box/dummy/dummy_sandbox.py +26 -0
  52. agentscope_runtime/sandbox/box/filesystem/__init__.py +0 -0
  53. agentscope_runtime/sandbox/box/filesystem/box/__init__.py +0 -0
  54. agentscope_runtime/sandbox/box/filesystem/filesystem_sandbox.py +87 -0
  55. agentscope_runtime/sandbox/box/sandbox.py +115 -0
  56. agentscope_runtime/sandbox/box/shared/__init__.py +0 -0
  57. agentscope_runtime/sandbox/box/shared/app.py +44 -0
  58. agentscope_runtime/sandbox/box/shared/dependencies/__init__.py +5 -0
  59. agentscope_runtime/sandbox/box/shared/dependencies/deps.py +22 -0
  60. agentscope_runtime/sandbox/box/shared/routers/__init__.py +12 -0
  61. agentscope_runtime/sandbox/box/shared/routers/generic.py +173 -0
  62. agentscope_runtime/sandbox/box/shared/routers/mcp.py +207 -0
  63. agentscope_runtime/sandbox/box/shared/routers/mcp_utils.py +153 -0
  64. agentscope_runtime/sandbox/box/shared/routers/runtime_watcher.py +187 -0
  65. agentscope_runtime/sandbox/box/shared/routers/workspace.py +325 -0
  66. agentscope_runtime/sandbox/box/training_box/__init__.py +0 -0
  67. agentscope_runtime/sandbox/box/training_box/base.py +120 -0
  68. agentscope_runtime/sandbox/box/training_box/env_service.py +752 -0
  69. agentscope_runtime/sandbox/box/training_box/environments/__init__.py +0 -0
  70. agentscope_runtime/sandbox/box/training_box/environments/appworld/appworld_env.py +987 -0
  71. agentscope_runtime/sandbox/box/training_box/registry.py +54 -0
  72. agentscope_runtime/sandbox/box/training_box/src/trajectory.py +278 -0
  73. agentscope_runtime/sandbox/box/training_box/training_box.py +219 -0
  74. agentscope_runtime/sandbox/build.py +213 -0
  75. agentscope_runtime/sandbox/client/__init__.py +5 -0
  76. agentscope_runtime/sandbox/client/http_client.py +527 -0
  77. agentscope_runtime/sandbox/client/training_client.py +265 -0
  78. agentscope_runtime/sandbox/constant.py +5 -0
  79. agentscope_runtime/sandbox/custom/__init__.py +16 -0
  80. agentscope_runtime/sandbox/custom/custom_sandbox.py +40 -0
  81. agentscope_runtime/sandbox/custom/example.py +37 -0
  82. agentscope_runtime/sandbox/enums.py +68 -0
  83. agentscope_runtime/sandbox/manager/__init__.py +4 -0
  84. agentscope_runtime/sandbox/manager/collections/__init__.py +22 -0
  85. agentscope_runtime/sandbox/manager/collections/base_mapping.py +20 -0
  86. agentscope_runtime/sandbox/manager/collections/base_queue.py +25 -0
  87. agentscope_runtime/sandbox/manager/collections/base_set.py +25 -0
  88. agentscope_runtime/sandbox/manager/collections/in_memory_mapping.py +22 -0
  89. agentscope_runtime/sandbox/manager/collections/in_memory_queue.py +28 -0
  90. agentscope_runtime/sandbox/manager/collections/in_memory_set.py +27 -0
  91. agentscope_runtime/sandbox/manager/collections/redis_mapping.py +26 -0
  92. agentscope_runtime/sandbox/manager/collections/redis_queue.py +27 -0
  93. agentscope_runtime/sandbox/manager/collections/redis_set.py +23 -0
  94. agentscope_runtime/sandbox/manager/container_clients/__init__.py +8 -0
  95. agentscope_runtime/sandbox/manager/container_clients/base_client.py +39 -0
  96. agentscope_runtime/sandbox/manager/container_clients/docker_client.py +170 -0
  97. agentscope_runtime/sandbox/manager/sandbox_manager.py +694 -0
  98. agentscope_runtime/sandbox/manager/server/__init__.py +0 -0
  99. agentscope_runtime/sandbox/manager/server/app.py +194 -0
  100. agentscope_runtime/sandbox/manager/server/config.py +68 -0
  101. agentscope_runtime/sandbox/manager/server/models.py +17 -0
  102. agentscope_runtime/sandbox/manager/storage/__init__.py +10 -0
  103. agentscope_runtime/sandbox/manager/storage/data_storage.py +16 -0
  104. agentscope_runtime/sandbox/manager/storage/local_storage.py +44 -0
  105. agentscope_runtime/sandbox/manager/storage/oss_storage.py +89 -0
  106. agentscope_runtime/sandbox/manager/utils.py +78 -0
  107. agentscope_runtime/sandbox/mcp_server.py +192 -0
  108. agentscope_runtime/sandbox/model/__init__.py +12 -0
  109. agentscope_runtime/sandbox/model/api.py +16 -0
  110. agentscope_runtime/sandbox/model/container.py +72 -0
  111. agentscope_runtime/sandbox/model/manager_config.py +158 -0
  112. agentscope_runtime/sandbox/registry.py +129 -0
  113. agentscope_runtime/sandbox/tools/__init__.py +12 -0
  114. agentscope_runtime/sandbox/tools/base/__init__.py +8 -0
  115. agentscope_runtime/sandbox/tools/base/tool.py +52 -0
  116. agentscope_runtime/sandbox/tools/browser/__init__.py +57 -0
  117. agentscope_runtime/sandbox/tools/browser/tool.py +597 -0
  118. agentscope_runtime/sandbox/tools/filesystem/__init__.py +32 -0
  119. agentscope_runtime/sandbox/tools/filesystem/tool.py +319 -0
  120. agentscope_runtime/sandbox/tools/function_tool.py +321 -0
  121. agentscope_runtime/sandbox/tools/mcp_tool.py +191 -0
  122. agentscope_runtime/sandbox/tools/sandbox_tool.py +104 -0
  123. agentscope_runtime/sandbox/tools/tool.py +123 -0
  124. agentscope_runtime/sandbox/tools/utils.py +68 -0
  125. agentscope_runtime/version.py +2 -0
  126. agentscope_runtime-0.1.0.dist-info/METADATA +327 -0
  127. agentscope_runtime-0.1.0.dist-info/RECORD +131 -0
  128. agentscope_runtime-0.1.0.dist-info/WHEEL +5 -0
  129. agentscope_runtime-0.1.0.dist-info/entry_points.txt +4 -0
  130. agentscope_runtime-0.1.0.dist-info/licenses/LICENSE +202 -0
  131. agentscope_runtime-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,425 @@
1
+ # -*- coding: utf-8 -*-
2
+ # pylint: disable=unused-argument
3
+ from a2a.types import (
4
+ Message as A2AMessage,
5
+ Part,
6
+ TextPart,
7
+ FilePart,
8
+ DataPart,
9
+ Artifact,
10
+ TaskStatus,
11
+ TaskState,
12
+ Task,
13
+ TaskStatusUpdateEvent,
14
+ TaskArtifactUpdateEvent,
15
+ TaskQueryParams,
16
+ AgentCard,
17
+ AgentCapabilities,
18
+ )
19
+
20
+ from ....agents import Agent
21
+ from ....schemas.agent_schemas import (
22
+ Message as AgentMessage,
23
+ Content,
24
+ TextContent,
25
+ ImageContent,
26
+ DataContent,
27
+ AgentRequest,
28
+ AgentResponse,
29
+ MessageType,
30
+ Role,
31
+ )
32
+
33
+
34
+ # Request conversion functions
35
+ # Functions to convert A2A protocol objects to internal Agent API objects
36
+
37
+
38
+ def a2a_message_to_agent_message(msg: A2AMessage) -> AgentMessage:
39
+ """
40
+ Convert A2A Message object to AgentAPI Message object
41
+
42
+ Args:
43
+ msg (A2AMessage): A2A protocol message object
44
+
45
+ Returns:
46
+ AgentMessage: Converted internal Agent API message object
47
+ """
48
+ contents = [a2a_part_to_agent_content(part) for part in msg.parts]
49
+
50
+ return AgentMessage(
51
+ role=msg.role,
52
+ content=contents,
53
+ id=msg.message_id,
54
+ type=MessageType.MESSAGE,
55
+ )
56
+
57
+
58
+ def a2a_part_to_agent_content(part: Part) -> Content:
59
+ """
60
+ Convert A2A protocol Part object to internal Content object
61
+
62
+ Args:
63
+ part (Part): A2A protocol part object
64
+
65
+ Returns:
66
+ Content: Converted internal content object
67
+
68
+ Raises:
69
+ ValueError: If the part type is unknown
70
+ """
71
+ # Unpack RootModel if exists
72
+ real_part = part.root if hasattr(part, "root") else part
73
+
74
+ if isinstance(real_part, TextPart):
75
+ return TextContent(text=real_part.text)
76
+ elif isinstance(real_part, FilePart):
77
+ # Assume ImageContent is equivalent to file, adjust if needed
78
+ return ImageContent(image_url=real_part.file.uri)
79
+ elif isinstance(real_part, DataPart):
80
+ return DataContent(data=real_part.data)
81
+ else:
82
+ raise ValueError(f"Unknown part type: {type(real_part)}")
83
+
84
+
85
+ def a2a_sendparams_to_agent_request(
86
+ params: dict,
87
+ stream: bool,
88
+ context_id: str = None,
89
+ ) -> AgentRequest:
90
+ """
91
+ Convert a2a MessageSendParams to agent-api AgentRequest
92
+
93
+ Args:
94
+ params (dict): MessageSendParams received from a2a protocol
95
+ stream (bool): Whether this request is in stream mode
96
+ (/message/send = False, /message/stream = True)
97
+ context_id (str, optional): Context ID if message is appended to
98
+ existing conversation
99
+
100
+ Returns:
101
+ AgentRequest: Converted agent request object
102
+ """
103
+ # 1. Convert a2a 'message' to agent-api 'Message' and wrap in list
104
+ a2a_msg = params["message"] # a2a Message
105
+ agent_api_msg = a2a_message_to_agent_message(
106
+ a2a_msg,
107
+ ) # Conversion function already implemented
108
+
109
+ # 2. Fill AgentRequest
110
+ req = AgentRequest(
111
+ input=[agent_api_msg],
112
+ stream=stream,
113
+ session_id=context_id or None,
114
+ # Other fields (model, top_p, temperature, tools...) can be extended
115
+ # later
116
+ )
117
+ return req
118
+
119
+
120
+ def a2a_taskqueryparams_to_agent_request(
121
+ params: "TaskQueryParams",
122
+ session_id: str = None,
123
+ ) -> "AgentRequest":
124
+ """
125
+ Convert TaskQueryParams to AgentRequest, only set session_id
126
+ Other fields are controlled by AgentRequest default values
127
+
128
+ Args:
129
+ params (TaskQueryParams): Task query parameters from a2a protocol
130
+ session_id (str, optional): Session ID for the request
131
+
132
+ Returns:
133
+ AgentRequest: Converted agent request object with only session_id set
134
+ """
135
+ return AgentRequest(
136
+ session_id=session_id or "",
137
+ # input, stream etc. use default values
138
+ response_id=TaskQueryParams.id,
139
+ )
140
+
141
+
142
+ # Response conversion functions
143
+ # Functions to convert internal Agent API objects to A2A protocol objects
144
+
145
+
146
+ def agent_content_to_a2a_part(content: Content) -> Part:
147
+ """
148
+ Convert internal Content object to A2A protocol Part object
149
+
150
+ Args:
151
+ content (Content): Internal content object
152
+
153
+ Returns:
154
+ Part: Converted A2A protocol part object
155
+
156
+ Raises:
157
+ ValueError: If the content type is unknown
158
+ """
159
+
160
+ # Dispatch conversion based on type
161
+ if isinstance(content, TextContent):
162
+ return Part(root=TextPart(text=content.text))
163
+ elif isinstance(content, ImageContent):
164
+ # Assume it's FilePart, adjust if FilePart structure is different
165
+ return Part(root=FilePart(url=content.image_url))
166
+ elif isinstance(content, DataContent):
167
+ return Part(root=DataPart(data=content.data))
168
+ else:
169
+ raise ValueError(f"Unknown content type: {type(content)}")
170
+
171
+
172
+ def agent_message_to_a2a_artifact(msg: AgentMessage) -> Artifact:
173
+ """
174
+ Convert AgentAPI Message to a2a Artifact
175
+
176
+ Args:
177
+ msg (AgentMessage): Agent API message object
178
+
179
+ Returns:
180
+ Artifact: Converted A2A protocol artifact object
181
+ """
182
+ # When content is empty, set parts to []
183
+ parts = [agent_content_to_a2a_part(c) for c in (msg.content or [])]
184
+
185
+ return Artifact(
186
+ artifact_id=msg.id,
187
+ name=msg.type, # Changed to type
188
+ description=None,
189
+ parts=parts,
190
+ metadata=None,
191
+ extensions=None,
192
+ )
193
+
194
+
195
+ def runstatus_to_a2a_taskstate(status: str) -> TaskState:
196
+ """
197
+ Map Internal RunStatus to a2a TaskState
198
+
199
+ Args:
200
+ status (str): Internal run status string
201
+
202
+ Returns:
203
+ TaskState: Mapped A2A task state
204
+ """
205
+ mapping = {
206
+ "Created": TaskState.submitted,
207
+ "Delta": TaskState.working,
208
+ "InProgress": TaskState.working,
209
+ "Completed": TaskState.completed,
210
+ "Canceled": TaskState.canceled,
211
+ "Failed": TaskState.failed,
212
+ "Rejected": TaskState.rejected,
213
+ "Unknown": TaskState.unknown,
214
+ # Add other extensions if needed
215
+ }
216
+ # Support case insensitive
217
+ status_key = status.strip().capitalize() if status else "Unknown"
218
+ return mapping.get(status_key, TaskState.unknown)
219
+
220
+
221
+ def agent_response_to_a2a_task(resp: AgentResponse) -> Task:
222
+ """
223
+ Convert AgentResponse object to a2a Task object.
224
+
225
+ Args:
226
+ resp (AgentResponse): Internal agent response object
227
+
228
+ Returns:
229
+ Task: Converted A2A protocol task object
230
+ """
231
+ # 1. ID mapping
232
+ task_id = resp.id
233
+
234
+ # 2. context_id
235
+ context_id = resp.session_id or ""
236
+
237
+ # 3. status (TaskStatus)
238
+ state = runstatus_to_a2a_taskstate(resp.status)
239
+ # message: a2a TaskStatus not filled for now
240
+ # timestamp: ISO8601
241
+ if resp.created_at:
242
+ from datetime import datetime
243
+
244
+ timestamp = (
245
+ datetime.utcfromtimestamp(resp.created_at).isoformat() + "Z"
246
+ )
247
+ else:
248
+ timestamp = None
249
+ status = TaskStatus(
250
+ state=state,
251
+ message=None,
252
+ timestamp=timestamp,
253
+ )
254
+
255
+ # 4. history: Empty for now
256
+ history = None
257
+
258
+ # 5. artifacts
259
+ artifacts = []
260
+ if resp.output:
261
+ artifacts = [agent_message_to_a2a_artifact(msg) for msg in resp.output]
262
+
263
+ # 6. metadata: Empty for now
264
+ metadata = None
265
+
266
+ # 7. kind: Fixed as 'task'
267
+ kind = "task"
268
+
269
+ return Task(
270
+ id=task_id,
271
+ context_id=context_id,
272
+ status=status,
273
+ history=history,
274
+ artifacts=artifacts,
275
+ metadata=metadata,
276
+ kind=kind,
277
+ )
278
+
279
+
280
+ def response_to_task_status_update_event(
281
+ response: AgentResponse,
282
+ ) -> TaskStatusUpdateEvent:
283
+ """
284
+ Convert AgentResponse (internal response) to a2a TaskStatusUpdateEvent.
285
+
286
+ Args:
287
+ response (AgentResponse): Internal agent response object
288
+
289
+ Returns:
290
+ TaskStatusUpdateEvent: Converted A2A protocol task status update event
291
+ """
292
+
293
+ # ---- 1. context_id
294
+ context_id = response.session_id or ""
295
+
296
+ # ---- 2. task_id
297
+ task_id = response.id
298
+
299
+ # ---- 3. status (TaskStatus)
300
+ state = runstatus_to_a2a_taskstate(response.status)
301
+ # timestamp (use created_at or completed_at as time record, prefer
302
+ # completed_at)
303
+ from datetime import datetime
304
+
305
+ ts = response.completed_at or response.created_at
306
+ timestamp = datetime.utcfromtimestamp(ts).isoformat() + "Z" if ts else None
307
+ status = TaskStatus(
308
+ state=state,
309
+ message=None,
310
+ timestamp=timestamp,
311
+ )
312
+
313
+ # ---- 4. final: Whether the streaming event is the final package
314
+ final_states = {"completed", "canceled", "failed", "rejected"}
315
+ final = str(response.status).lower() in final_states
316
+
317
+ # ---- 5. kind always 'status-update'
318
+ kind = "status-update"
319
+
320
+ # ---- 6. metadata (for extension, None for now)
321
+ metadata = None
322
+
323
+ return TaskStatusUpdateEvent(
324
+ context_id=context_id,
325
+ task_id=task_id,
326
+ status=status,
327
+ kind=kind,
328
+ final=final,
329
+ metadata=metadata,
330
+ )
331
+
332
+
333
+ def content_to_task_artifact_update_event(
334
+ content: "Content",
335
+ context_id: str = "",
336
+ task_id: str = None,
337
+ append: bool = False,
338
+ last_chunk: bool = False,
339
+ ) -> "TaskArtifactUpdateEvent":
340
+ """
341
+ Convert single Content (TextContent/ImageContent/DataContent) to
342
+ TaskArtifactUpdateEvent. If delta=false, should not return
343
+ task_artifact_update_event
344
+
345
+ Args:
346
+ content: SSE returned content, including delta and non-delta types
347
+ context_id: Corresponds to agent api sessionId, needs external input
348
+ task_id: Currently equivalent to msg_id, or not passed
349
+ append: Used to determine if current artifact is new or first
350
+ last_chunk: Used to determine if current content is the last one
351
+
352
+ Returns:
353
+ TaskArtifactUpdateEvent: Converted A2A protocol task artifact update
354
+ event
355
+ """
356
+ part = agent_content_to_a2a_part(content)
357
+ artifact_id = (
358
+ content.msg_id or ""
359
+ ) # Content's msg_id may be None, need fallback
360
+
361
+ artifact = Artifact(
362
+ artifact_id=artifact_id,
363
+ name=content.type, # "text", "image", "data"
364
+ description=None,
365
+ parts=[part],
366
+ metadata=None,
367
+ extensions=None,
368
+ )
369
+
370
+ return TaskArtifactUpdateEvent(
371
+ append=append,
372
+ artifact=artifact,
373
+ context_id=context_id,
374
+ kind="artifact-update",
375
+ last_chunk=last_chunk,
376
+ task_id=task_id or artifact_id,
377
+ )
378
+
379
+
380
+ def agent_role_to_a2a_role(role: str):
381
+ if role == Role.ASSISTANT:
382
+ return "agent"
383
+ elif role == Role.USER:
384
+ return "user"
385
+ elif role == Role.SYSTEM:
386
+ return "system"
387
+ else:
388
+ return "unknown"
389
+
390
+
391
+ def agent_message_to_a2a_message(msg: "AgentMessage") -> "A2AMessage":
392
+ """
393
+ Convert AgentAPI Message object to a2a protocol Message object
394
+
395
+ Args:
396
+ msg (AgentAPIMessage): Agent API message object
397
+
398
+ Returns:
399
+ A2AMessage: Converted A2A protocol message object
400
+ """
401
+ parts = [agent_content_to_a2a_part(content) for content in msg.content]
402
+ return A2AMessage(
403
+ message_id=msg.id,
404
+ role=agent_role_to_a2a_role(msg.role),
405
+ parts=parts,
406
+ # Others can be added as needed, such as metadata
407
+ )
408
+
409
+
410
+ def agent_card(
411
+ agent: Agent,
412
+ url: str,
413
+ version: str = "1.0.0",
414
+ **kwargs,
415
+ ) -> AgentCard:
416
+ return AgentCard(
417
+ name=agent.name,
418
+ description=agent.description,
419
+ url=url,
420
+ version=version,
421
+ capabilities=AgentCapabilities(streaming=False),
422
+ default_input_modes=["application/json"],
423
+ default_output_modes=["application/json"],
424
+ **kwargs,
425
+ )
@@ -0,0 +1,69 @@
1
+ # -*- coding: utf-8 -*-
2
+ # pylint: disable=unused-argument
3
+ import logging
4
+ import traceback
5
+ from typing import Callable
6
+
7
+ from a2a.server.agent_execution import AgentExecutor, RequestContext
8
+ from a2a.server.events import EventQueue
9
+ from a2a.types import UnsupportedOperationError
10
+ from a2a.utils.errors import ServerError
11
+
12
+ from agentscope_runtime.engine.deployers.adapter.a2a.a2a_adapter_utils import (
13
+ agent_message_to_a2a_message,
14
+ )
15
+ from agentscope_runtime.engine.schemas.agent_schemas import (
16
+ AgentRequest,
17
+ RunStatus,
18
+ )
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ class A2AExecutor(AgentExecutor):
24
+ def __init__(self, func: Callable, **kwargs):
25
+ self._func = func
26
+
27
+ async def execute(
28
+ self,
29
+ context: RequestContext,
30
+ event_queue: EventQueue,
31
+ ) -> None:
32
+ query = context.get_user_input()
33
+
34
+ request = AgentRequest.model_validate(
35
+ {
36
+ "session_id": context.context_id,
37
+ "response_id": context.task_id,
38
+ "input": [
39
+ {
40
+ "role": "user",
41
+ "content": [
42
+ {
43
+ "type": "text",
44
+ "text": query,
45
+ },
46
+ ],
47
+ },
48
+ ],
49
+ "stream": True,
50
+ },
51
+ )
52
+
53
+ try:
54
+ async for event in self._func(request=request):
55
+ if event.object == "response":
56
+ if event.status == RunStatus.Completed:
57
+ if event.output:
58
+ message = event.output[len(event.output) - 1]
59
+ a2a_message = agent_message_to_a2a_message(message)
60
+ await event_queue.enqueue_event(a2a_message)
61
+ except Exception as e:
62
+ logger.error(f"An error occurred: {e}, {traceback.format_exc()}")
63
+
64
+ async def cancel(
65
+ self,
66
+ context: RequestContext,
67
+ event_queue: EventQueue,
68
+ ) -> None:
69
+ raise ServerError(error=UnsupportedOperationError())
@@ -0,0 +1,60 @@
1
+ # -*- coding: utf-8 -*-
2
+ from typing import Callable
3
+
4
+ from a2a.server.apps import A2AFastAPIApplication
5
+ from a2a.server.request_handlers import DefaultRequestHandler
6
+ from a2a.server.tasks import InMemoryTaskStore
7
+ from a2a.types import AgentCard, AgentCapabilities, AgentSkill
8
+
9
+ from .a2a_agent_adapter import A2AExecutor
10
+ from ..protocol_adapter import ProtocolAdapter
11
+ from ....agents import Agent
12
+
13
+
14
+ class A2AFastAPIDefaultAdapter(ProtocolAdapter):
15
+ def __init__(self, agent, **kwargs):
16
+ super().__init__(**kwargs)
17
+ self._agent = agent
18
+
19
+ def add_endpoint(self, app, func: Callable, **kwargs):
20
+ request_handler = DefaultRequestHandler(
21
+ agent_executor=A2AExecutor(func=func),
22
+ task_store=InMemoryTaskStore(),
23
+ )
24
+
25
+ agent_card = self.get_agent_card(self._agent)
26
+
27
+ server = A2AFastAPIApplication(
28
+ agent_card=agent_card,
29
+ http_handler=request_handler,
30
+ )
31
+
32
+ server.add_routes_to_app(app)
33
+
34
+ def get_agent_card(self, agent: Agent) -> AgentCard:
35
+ capabilities = AgentCapabilities(
36
+ streaming=False,
37
+ push_notifications=False,
38
+ )
39
+ skill = AgentSkill(
40
+ id="dialog",
41
+ name="Natural Language Dialog Skill",
42
+ description="Enables natural language conversation and dialogue "
43
+ "with users",
44
+ tags=["natural language", "dialog", "conversation"],
45
+ examples=[
46
+ "Hello, how are you?",
47
+ "Can you help me with something?",
48
+ ],
49
+ )
50
+
51
+ return AgentCard(
52
+ capabilities=capabilities,
53
+ skills=[skill],
54
+ name=agent.name,
55
+ description=agent.description,
56
+ default_input_modes=["text"],
57
+ default_output_modes=["text"],
58
+ url="http://127.0.0.1:8090/",
59
+ version="1.0.0",
60
+ )
@@ -0,0 +1,24 @@
1
+ # -*- coding: utf-8 -*-
2
+ from abc import ABC, abstractmethod
3
+ from typing import Any, Callable
4
+
5
+
6
+ class ProtocolAdapter(ABC):
7
+ def __init__(self, **kwargs):
8
+ self._kwargs = kwargs
9
+
10
+ @abstractmethod
11
+ def add_endpoint(self, app, func: Callable, **kwargs) -> Any:
12
+ """
13
+ Add an endpoint to the protocol adapter.
14
+
15
+ This method should be implemented by subclasses to provide
16
+ protocol-specific endpoint addition functionality.
17
+
18
+ Args:
19
+ *args: Variable length argument list for endpoint configuration
20
+ **kwargs: Arbitrary keyword arguments for endpoint configuration
21
+
22
+ Returns:
23
+ Any: The result of adding the endpoint, implementation-dependent
24
+ """
@@ -0,0 +1,17 @@
1
+ # -*- coding: utf-8 -*-
2
+ import uuid
3
+ from abc import abstractmethod, ABC
4
+ from typing import Dict
5
+
6
+
7
+ # there is not many attributes in it, consider it as interface, instead of
8
+ # pydantic BaseModel
9
+ class DeployManager(ABC):
10
+ def __init__(self):
11
+ self.deploy_id = str(uuid.uuid4())
12
+
13
+ @abstractmethod
14
+ async def deploy(self, *args, **kwargs) -> Dict[str, str]:
15
+ """Deploy the service and return a dictionary with deploy_id and
16
+ URL."""
17
+ raise NotImplementedError