agentscope-runtime 1.0.5.post1__py3-none-any.whl → 1.1.0b3__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 (71) hide show
  1. agentscope_runtime/__init__.py +3 -0
  2. agentscope_runtime/adapters/agentscope/message.py +85 -295
  3. agentscope_runtime/adapters/agentscope/stream.py +133 -3
  4. agentscope_runtime/adapters/agno/message.py +11 -2
  5. agentscope_runtime/adapters/agno/stream.py +1 -0
  6. agentscope_runtime/adapters/langgraph/__init__.py +1 -3
  7. agentscope_runtime/adapters/langgraph/message.py +11 -106
  8. agentscope_runtime/adapters/langgraph/stream.py +1 -0
  9. agentscope_runtime/adapters/ms_agent_framework/message.py +11 -1
  10. agentscope_runtime/adapters/ms_agent_framework/stream.py +1 -0
  11. agentscope_runtime/adapters/text/stream.py +1 -0
  12. agentscope_runtime/common/container_clients/agentrun_client.py +0 -3
  13. agentscope_runtime/common/container_clients/boxlite_client.py +26 -15
  14. agentscope_runtime/common/container_clients/fc_client.py +0 -11
  15. agentscope_runtime/common/utils/deprecation.py +14 -17
  16. agentscope_runtime/common/utils/logging.py +44 -0
  17. agentscope_runtime/engine/app/agent_app.py +5 -5
  18. agentscope_runtime/engine/app/celery_mixin.py +43 -4
  19. agentscope_runtime/engine/deployers/adapter/agui/__init__.py +8 -1
  20. agentscope_runtime/engine/deployers/adapter/agui/agui_adapter_utils.py +6 -1
  21. agentscope_runtime/engine/deployers/adapter/agui/agui_protocol_adapter.py +2 -2
  22. agentscope_runtime/engine/deployers/utils/service_utils/fastapi_factory.py +13 -0
  23. agentscope_runtime/engine/runner.py +31 -6
  24. agentscope_runtime/engine/schemas/agent_schemas.py +28 -0
  25. agentscope_runtime/engine/services/sandbox/sandbox_service.py +41 -9
  26. agentscope_runtime/sandbox/box/base/base_sandbox.py +4 -0
  27. agentscope_runtime/sandbox/box/browser/browser_sandbox.py +4 -0
  28. agentscope_runtime/sandbox/box/dummy/dummy_sandbox.py +9 -2
  29. agentscope_runtime/sandbox/box/filesystem/filesystem_sandbox.py +4 -0
  30. agentscope_runtime/sandbox/box/gui/gui_sandbox.py +5 -1
  31. agentscope_runtime/sandbox/box/mobile/mobile_sandbox.py +4 -0
  32. agentscope_runtime/sandbox/box/sandbox.py +122 -13
  33. agentscope_runtime/sandbox/client/async_http_client.py +1 -0
  34. agentscope_runtime/sandbox/client/base.py +0 -1
  35. agentscope_runtime/sandbox/client/http_client.py +0 -2
  36. agentscope_runtime/sandbox/manager/heartbeat_mixin.py +486 -0
  37. agentscope_runtime/sandbox/manager/sandbox_manager.py +740 -153
  38. agentscope_runtime/sandbox/manager/server/app.py +18 -11
  39. agentscope_runtime/sandbox/manager/server/config.py +10 -2
  40. agentscope_runtime/sandbox/mcp_server.py +0 -1
  41. agentscope_runtime/sandbox/model/__init__.py +2 -1
  42. agentscope_runtime/sandbox/model/container.py +90 -3
  43. agentscope_runtime/sandbox/model/manager_config.py +45 -1
  44. agentscope_runtime/version.py +1 -1
  45. {agentscope_runtime-1.0.5.post1.dist-info → agentscope_runtime-1.1.0b3.dist-info}/METADATA +37 -54
  46. {agentscope_runtime-1.0.5.post1.dist-info → agentscope_runtime-1.1.0b3.dist-info}/RECORD +50 -69
  47. agentscope_runtime/adapters/agentscope/long_term_memory/__init__.py +0 -6
  48. agentscope_runtime/adapters/agentscope/long_term_memory/_long_term_memory_adapter.py +0 -258
  49. agentscope_runtime/adapters/agentscope/memory/__init__.py +0 -6
  50. agentscope_runtime/adapters/agentscope/memory/_memory_adapter.py +0 -152
  51. agentscope_runtime/engine/services/agent_state/__init__.py +0 -25
  52. agentscope_runtime/engine/services/agent_state/redis_state_service.py +0 -166
  53. agentscope_runtime/engine/services/agent_state/state_service.py +0 -179
  54. agentscope_runtime/engine/services/agent_state/state_service_factory.py +0 -52
  55. agentscope_runtime/engine/services/memory/__init__.py +0 -33
  56. agentscope_runtime/engine/services/memory/mem0_memory_service.py +0 -128
  57. agentscope_runtime/engine/services/memory/memory_service.py +0 -292
  58. agentscope_runtime/engine/services/memory/memory_service_factory.py +0 -126
  59. agentscope_runtime/engine/services/memory/redis_memory_service.py +0 -290
  60. agentscope_runtime/engine/services/memory/reme_personal_memory_service.py +0 -109
  61. agentscope_runtime/engine/services/memory/reme_task_memory_service.py +0 -11
  62. agentscope_runtime/engine/services/memory/tablestore_memory_service.py +0 -301
  63. agentscope_runtime/engine/services/session_history/__init__.py +0 -32
  64. agentscope_runtime/engine/services/session_history/redis_session_history_service.py +0 -283
  65. agentscope_runtime/engine/services/session_history/session_history_service.py +0 -267
  66. agentscope_runtime/engine/services/session_history/session_history_service_factory.py +0 -73
  67. agentscope_runtime/engine/services/session_history/tablestore_session_history_service.py +0 -288
  68. {agentscope_runtime-1.0.5.post1.dist-info → agentscope_runtime-1.1.0b3.dist-info}/WHEEL +0 -0
  69. {agentscope_runtime-1.0.5.post1.dist-info → agentscope_runtime-1.1.0b3.dist-info}/entry_points.txt +0 -0
  70. {agentscope_runtime-1.0.5.post1.dist-info → agentscope_runtime-1.1.0b3.dist-info}/licenses/LICENSE +0 -0
  71. {agentscope_runtime-1.0.5.post1.dist-info → agentscope_runtime-1.1.0b3.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,7 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  from .version import __version__
3
+ from .common.utils.logging import setup_logger
4
+
5
+ setup_logger()
3
6
 
4
7
  __all__ = ["__version__"]
@@ -4,8 +4,9 @@
4
4
  import json
5
5
 
6
6
  from collections import OrderedDict
7
- from typing import Union, List
7
+ from typing import Union, List, Callable, Optional, Dict, Literal
8
8
  from urllib.parse import urlparse
9
+ from typing_extensions import TypedDict, Required
9
10
 
10
11
  from mcp.types import CallToolResult
11
12
  from agentscope.message import (
@@ -24,317 +25,45 @@ from agentscope.mcp._client_base import MCPClientBase
24
25
 
25
26
  from ...engine.schemas.agent_schemas import (
26
27
  Message,
27
- FunctionCall,
28
- FunctionCallOutput,
29
28
  MessageType,
30
29
  )
31
- from ...engine.helpers.agent_api_builder import ResponseBuilder
32
30
 
33
31
 
34
- def matches_typed_dict_structure(obj, typed_dict_cls):
35
- if not isinstance(obj, dict):
36
- return False
37
- expected_keys = set(typed_dict_cls.__annotations__.keys())
38
- return expected_keys == set(obj.keys())
32
+ # TODO: support in core framework
33
+ class FileBlock(TypedDict, total=False):
34
+ """The file block"""
39
35
 
36
+ type: Required[Literal["file"]]
37
+ """The type of the block"""
40
38
 
41
- def agentscope_msg_to_message(
42
- messages: Union[Msg, List[Msg]],
43
- ) -> List[Message]:
44
- """
45
- Convert AgentScope Msg(s) into one or more runtime Message objects
39
+ source: Required[Base64Source | URLSource]
40
+ """The src of the file"""
46
41
 
47
- Args:
48
- messages: AgentScope message(s) from streaming.
42
+ filename: Optional[str]
43
+ """Optional filename hint (e.g. `report.pdf`)"""
49
44
 
50
- Returns:
51
- List[Message]: One or more constructed runtime Message objects.
52
- """
53
- if isinstance(messages, Msg):
54
- msgs = [messages]
55
- elif isinstance(messages, list):
56
- msgs = messages
57
- else:
58
- raise TypeError(f"Expected Msg or list[Msg], got {type(messages)}")
59
-
60
- results: List[Message] = []
61
-
62
- for msg in msgs:
63
- role = msg.role or "assistant"
64
-
65
- if isinstance(msg.content, str):
66
- # Only text
67
- rb = ResponseBuilder()
68
- mb = rb.create_message_builder(
69
- role=role,
70
- message_type=MessageType.MESSAGE,
71
- )
72
- # add meta field to store old id and name
73
- mb.message.metadata = {
74
- "original_id": msg.id,
75
- "original_name": msg.name,
76
- "metadata": msg.metadata,
77
- }
78
- cb = mb.create_content_builder(content_type="text")
79
- cb.set_text(msg.content)
80
- cb.complete()
81
- mb.complete()
82
- results.append(mb.get_message_data())
83
- continue
84
-
85
- # msg.content is a list of blocks
86
- # We group blocks by high-level message type
87
- current_mb = None
88
- current_type = None
89
-
90
- for block in msg.content:
91
- if isinstance(block, dict):
92
- btype = block.get("type", "text")
93
- else:
94
- continue
95
-
96
- if btype == "text":
97
- # Create/continue MESSAGE type
98
- if current_type != MessageType.MESSAGE:
99
- if current_mb:
100
- current_mb.complete()
101
- results.append(current_mb.get_message_data())
102
- rb = ResponseBuilder()
103
- current_mb = rb.create_message_builder(
104
- role=role,
105
- message_type=MessageType.MESSAGE,
106
- )
107
- # add meta field to store old id and name
108
- current_mb.message.metadata = {
109
- "original_id": msg.id,
110
- "original_name": msg.name,
111
- "metadata": msg.metadata,
112
- }
113
- current_type = MessageType.MESSAGE
114
- cb = current_mb.create_content_builder(content_type="text")
115
- cb.set_text(block.get("text", ""))
116
- cb.complete()
117
-
118
- elif btype == "thinking":
119
- # Create/continue REASONING type
120
- if current_type != MessageType.REASONING:
121
- if current_mb:
122
- current_mb.complete()
123
- results.append(current_mb.get_message_data())
124
- rb = ResponseBuilder()
125
- current_mb = rb.create_message_builder(
126
- role=role,
127
- message_type=MessageType.REASONING,
128
- )
129
- # add meta field to store old id and name
130
- current_mb.message.metadata = {
131
- "original_id": msg.id,
132
- "original_name": msg.name,
133
- "metadata": msg.metadata,
134
- }
135
- current_type = MessageType.REASONING
136
- cb = current_mb.create_content_builder(content_type="text")
137
- cb.set_text(block.get("thinking", ""))
138
- cb.complete()
139
-
140
- elif btype == "tool_use":
141
- # Always start a new PLUGIN_CALL message
142
- if current_mb:
143
- current_mb.complete()
144
- results.append(current_mb.get_message_data())
145
- rb = ResponseBuilder()
146
- current_mb = rb.create_message_builder(
147
- role=role,
148
- message_type=MessageType.PLUGIN_CALL,
149
- )
150
- # add meta field to store old id and name
151
- current_mb.message.metadata = {
152
- "original_id": msg.id,
153
- "original_name": msg.name,
154
- "metadata": msg.metadata,
155
- }
156
- current_type = MessageType.PLUGIN_CALL
157
- cb = current_mb.create_content_builder(content_type="data")
158
-
159
- if isinstance(block.get("input"), (dict, list)):
160
- arguments = json.dumps(block.get("input"))
161
- else:
162
- arguments = block.get("input")
163
-
164
- call_data = FunctionCall(
165
- call_id=block.get("id"),
166
- name=block.get("name"),
167
- arguments=arguments,
168
- ).model_dump()
169
- cb.set_data(call_data)
170
- cb.complete()
171
-
172
- elif btype == "tool_result":
173
- # Always start a new PLUGIN_CALL_OUTPUT message
174
- if current_mb:
175
- current_mb.complete()
176
- results.append(current_mb.get_message_data())
177
- rb = ResponseBuilder()
178
- current_mb = rb.create_message_builder(
179
- role=role,
180
- message_type=MessageType.PLUGIN_CALL_OUTPUT,
181
- )
182
- # add meta field to store old id and name
183
- current_mb.message.metadata = {
184
- "original_id": msg.id,
185
- "original_name": msg.name,
186
- "metadata": msg.metadata,
187
- }
188
- current_type = MessageType.PLUGIN_CALL_OUTPUT
189
- cb = current_mb.create_content_builder(content_type="data")
190
-
191
- if isinstance(block.get("output"), (dict, list)):
192
- output = json.dumps(block.get("output"))
193
- else:
194
- output = block.get("output")
195
-
196
- output_data = FunctionCallOutput(
197
- call_id=block.get("id"),
198
- name=block.get("name"),
199
- output=output,
200
- ).model_dump(exclude_none=True)
201
- cb.set_data(output_data)
202
- cb.complete()
203
-
204
- elif btype == "image":
205
- # Create/continue MESSAGE type with image
206
- if current_type != MessageType.MESSAGE:
207
- if current_mb:
208
- current_mb.complete()
209
- results.append(current_mb.get_message_data())
210
- rb = ResponseBuilder()
211
- current_mb = rb.create_message_builder(
212
- role=role,
213
- message_type=MessageType.MESSAGE,
214
- )
215
- # add meta field to store old id and name
216
- current_mb.message.metadata = {
217
- "original_id": msg.id,
218
- "original_name": msg.name,
219
- "metadata": msg.metadata,
220
- }
221
- current_type = MessageType.MESSAGE
222
- cb = current_mb.create_content_builder(content_type="image")
223
-
224
- if (
225
- isinstance(block.get("source"), dict)
226
- and block.get("source", {}).get("type") == "url"
227
- ):
228
- cb.set_image_url(block.get("source", {}).get("url"))
229
-
230
- elif (
231
- isinstance(block.get("source"), dict)
232
- and block.get("source").get(
233
- "type",
234
- )
235
- == "base64"
236
- ):
237
- media_type = block.get("source", {}).get(
238
- "media_type",
239
- "image/jpeg",
240
- )
241
- base64_data = block.get("source", {}).get("data", "")
242
- url = f"data:{media_type};base64,{base64_data}"
243
- cb.set_image_url(url)
244
-
245
- cb.complete()
246
-
247
- elif btype == "audio":
248
- # Create/continue MESSAGE type with audio
249
- if current_type != MessageType.MESSAGE:
250
- if current_mb:
251
- current_mb.complete()
252
- results.append(current_mb.get_message_data())
253
- rb = ResponseBuilder()
254
- current_mb = rb.create_message_builder(
255
- role=role,
256
- message_type=MessageType.MESSAGE,
257
- )
258
- # add meta field to store old id and name
259
- current_mb.message.metadata = {
260
- "original_id": msg.id,
261
- "original_name": msg.name,
262
- "metadata": msg.metadata,
263
- }
264
- current_type = MessageType.MESSAGE
265
- cb = current_mb.create_content_builder(content_type="audio")
266
- # URLSource runtime check (dict with type == "url")
267
- if (
268
- isinstance(block.get("source"), dict)
269
- and block.get("source", {}).get(
270
- "type",
271
- )
272
- == "url"
273
- ):
274
- url = block.get("source", {}).get("url")
275
- cb.content.data = url
276
- try:
277
- cb.content.format = urlparse(url).path.split(".")[-1]
278
- except (AttributeError, IndexError, ValueError):
279
- cb.content.format = None
280
-
281
- # Base64Source runtime check (dict with type == "base64")
282
- elif (
283
- isinstance(block.get("source"), dict)
284
- and block.get("source").get(
285
- "type",
286
- )
287
- == "base64"
288
- ):
289
- media_type = block.get("source", {}).get(
290
- "media_type",
291
- )
292
- base64_data = block.get("source", {}).get("data", "")
293
- url = f"data:{media_type};base64,{base64_data}"
294
-
295
- cb.content.data = url
296
- cb.content.format = media_type
297
-
298
- cb.complete()
299
45
 
300
- else:
301
- # Fallback to MESSAGE type
302
- if current_type != MessageType.MESSAGE:
303
- if current_mb:
304
- current_mb.complete()
305
- results.append(current_mb.get_message_data())
306
- rb = ResponseBuilder()
307
- current_mb = rb.create_message_builder(
308
- role=role,
309
- message_type=MessageType.MESSAGE,
310
- )
311
- # add meta field to store old id and name
312
- current_mb.message.metadata = {
313
- "original_id": msg.id,
314
- "original_name": msg.name,
315
- "metadata": msg.metadata,
316
- }
317
- current_type = MessageType.MESSAGE
318
- cb = current_mb.create_content_builder(content_type="text")
319
- cb.set_text(str(block))
320
- cb.complete()
321
-
322
- # finalize last open message builder
323
- if current_mb:
324
- current_mb.complete()
325
- results.append(current_mb.get_message_data())
326
-
327
- return results
46
+ def matches_typed_dict_structure(obj, typed_dict_cls):
47
+ if not isinstance(obj, dict):
48
+ return False
49
+ expected_keys = set(typed_dict_cls.__annotations__.keys())
50
+ return expected_keys == set(obj.keys())
328
51
 
329
52
 
330
53
  def message_to_agentscope_msg(
331
54
  messages: Union[Message, List[Message]],
55
+ type_converters: Optional[Dict[str, Callable]] = None,
332
56
  ) -> Union[Msg, List[Msg]]:
333
57
  """
334
58
  Convert AgentScope runtime Message(s) to AgentScope Msg(s).
335
59
 
336
60
  Args:
337
61
  messages: A single AgentScope runtime Message or list of Messages.
62
+ type_converters: Optional mapping from ``message.type`` to a callable
63
+ ``converter(message)``. When provided and the current
64
+ ``message.type`` exists in the mapping, the corresponding converter
65
+ will be used and the built-in conversion logic will be skipped for
66
+ that message.
338
67
 
339
68
  Returns:
340
69
  A single Msg object or a list of Msg objects.
@@ -351,6 +80,10 @@ def message_to_agentscope_msg(
351
80
  return default
352
81
 
353
82
  def _convert_one(message: Message) -> Msg:
83
+ # Used for custom conversion
84
+ if type_converters and message.type in type_converters:
85
+ return type_converters[message.type](message)
86
+
354
87
  # Normalize role
355
88
  if message.role == "tool":
356
89
  role_label = "system" # AgentScope not support tool as role
@@ -474,8 +207,8 @@ def message_to_agentscope_msg(
474
207
  "image": (ImageBlock, "image_url"),
475
208
  "audio": (AudioBlock, "data"),
476
209
  "data": (TextBlock, "data"),
477
- # "video": (VideoBlock, "video_url", True),
478
- # TODO: support video
210
+ "video": (VideoBlock, "video_url"),
211
+ "file": (FileBlock, "file_url"),
479
212
  }
480
213
 
481
214
  msg_content = []
@@ -547,6 +280,63 @@ def message_to_agentscope_msg(
547
280
  msg_content.append(
548
281
  block_cls(type=cnt_type, source=base64_source),
549
282
  )
283
+ elif cnt_type == "video":
284
+ if (
285
+ value
286
+ and isinstance(value, str)
287
+ and value.startswith("data:")
288
+ ):
289
+ mediatype_part = value.split(";")[0].replace(
290
+ "data:",
291
+ "",
292
+ )
293
+ base64_data = value.split(",")[1]
294
+ base64_source = Base64Source(
295
+ type="base64",
296
+ media_type=mediatype_part,
297
+ data=base64_data,
298
+ )
299
+ msg_content.append(
300
+ block_cls(type=cnt_type, source=base64_source),
301
+ )
302
+ else:
303
+ url_source = URLSource(type="url", url=value)
304
+ msg_content.append(
305
+ block_cls(type=cnt_type, source=url_source),
306
+ )
307
+ elif cnt_type == "file":
308
+ filename = cnt.filename
309
+ if (
310
+ value
311
+ and isinstance(value, str)
312
+ and value.startswith("data:")
313
+ ):
314
+ mediatype_part = value.split(";")[0].replace(
315
+ "data:",
316
+ "",
317
+ )
318
+ base64_data = value.split(",")[1]
319
+ base64_source = Base64Source(
320
+ type="base64",
321
+ media_type=mediatype_part,
322
+ data=base64_data,
323
+ )
324
+ msg_content.append(
325
+ block_cls(
326
+ type=cnt_type,
327
+ source=base64_source,
328
+ filename=filename,
329
+ ),
330
+ )
331
+ else:
332
+ url_source = URLSource(type="url", url=value)
333
+ msg_content.append(
334
+ block_cls(
335
+ type=cnt_type,
336
+ source=url_source,
337
+ filename=filename,
338
+ ),
339
+ )
550
340
  else:
551
341
  # text & data
552
342
  if isinstance(value, str):
@@ -1,9 +1,10 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  # pylint: disable=too-many-nested-blocks,too-many-branches,too-many-statements
3
3
  import copy
4
+ import inspect
4
5
  import json
5
6
 
6
- from typing import AsyncIterator, Tuple, List, Union
7
+ from typing import AsyncIterator, Tuple, List, Union, Optional, Callable, Dict
7
8
  from urllib.parse import urlparse
8
9
 
9
10
  from agentscope import setup_logger
@@ -16,7 +17,9 @@ from ...engine.schemas.agent_schemas import (
16
17
  TextContent,
17
18
  ImageContent,
18
19
  AudioContent,
20
+ VideoContent,
19
21
  DataContent,
22
+ FileContent,
20
23
  McpCall,
21
24
  McpCallOutput,
22
25
  FunctionCall,
@@ -29,6 +32,8 @@ setup_logger("ERROR")
29
32
 
30
33
  async def adapt_agentscope_message_stream(
31
34
  source_stream: AsyncIterator[Tuple[Msg, bool]],
35
+ type_converters: Optional[Dict[str, Callable]] = None,
36
+ **kwargs, # pylint:disable=unused-argument
32
37
  ) -> AsyncIterator[Union[Message, Content]]:
33
38
  # Initialize variables to avoid uncaught errors
34
39
  msg_id = None
@@ -136,6 +141,44 @@ async def adapt_agentscope_message_stream(
136
141
  index = text_delta_content.index
137
142
  yield text_delta_content
138
143
  elif isinstance(element, dict):
144
+ # Used for custom conversion
145
+ if (
146
+ type_converters
147
+ and element.get("type") in type_converters
148
+ ):
149
+ blk_type = element.get("type")
150
+ if not isinstance(blk_type, str):
151
+ continue
152
+ fn = type_converters[blk_type]
153
+ # Send message, element, last, tool_start, metadata
154
+ # and usage
155
+ out = fn(
156
+ element,
157
+ message,
158
+ last,
159
+ tool_start,
160
+ metadata,
161
+ usage,
162
+ )
163
+ # Case 1: async generator / async iterator
164
+ if hasattr(out, "__aiter__"):
165
+ async for ev in out:
166
+ yield ev
167
+ continue
168
+
169
+ # Case 2: sync generator / iterator
170
+ if inspect.isgenerator(out):
171
+ for ev in out:
172
+ yield ev
173
+ continue
174
+
175
+ # Only generator styles are supported
176
+ raise TypeError(
177
+ f"type_converters['{blk_type}'] must return a "
178
+ f"generator/iterator or an async generator/async "
179
+ f"iterator, got: {type(out)}",
180
+ )
181
+
139
182
  if element.get("type") == "text": # Text
140
183
  text = element.get(
141
184
  "text",
@@ -446,7 +489,12 @@ async def adapt_agentscope_message_stream(
446
489
  == "url"
447
490
  ):
448
491
  kwargs.update(
449
- {"image_url": element.get("source")},
492
+ {
493
+ "image_url": element.get(
494
+ "source",
495
+ {},
496
+ ).get("url"),
497
+ },
450
498
  )
451
499
 
452
500
  elif (
@@ -471,7 +519,7 @@ async def adapt_agentscope_message_stream(
471
519
  index=index,
472
520
  **kwargs,
473
521
  )
474
- elif element.get("text") == "audio":
522
+ elif element.get("type") == "audio":
475
523
  kwargs = {}
476
524
  if (
477
525
  isinstance(element.get("source"), dict)
@@ -515,6 +563,88 @@ async def adapt_agentscope_message_stream(
515
563
  index=index,
516
564
  **kwargs,
517
565
  )
566
+ elif element.get("type") == "video":
567
+ kwargs = {}
568
+ if (
569
+ isinstance(element.get("source"), dict)
570
+ and element.get("source", {}).get(
571
+ "type",
572
+ )
573
+ == "url"
574
+ ):
575
+ kwargs.update(
576
+ {
577
+ "video_url": element.get(
578
+ "source",
579
+ {},
580
+ ).get("url"),
581
+ },
582
+ )
583
+
584
+ elif (
585
+ isinstance(element.get("source"), dict)
586
+ and element.get("source").get(
587
+ "type",
588
+ )
589
+ == "base64"
590
+ ):
591
+ media_type = element.get("source", {}).get(
592
+ "media_type",
593
+ "video/mp4",
594
+ )
595
+ base64_data = element.get("source", {}).get(
596
+ "data",
597
+ "",
598
+ )
599
+ url = f"data:{media_type};base64,{base64_data}"
600
+ kwargs.update({"video_url": url})
601
+ delta_content = VideoContent(
602
+ delta=True,
603
+ index=index,
604
+ **kwargs,
605
+ )
606
+ elif element.get("type") == "file":
607
+ kwargs = {
608
+ "filename": element.get("filename"),
609
+ }
610
+ if (
611
+ isinstance(element.get("source"), dict)
612
+ and element.get("source", {}).get(
613
+ "type",
614
+ )
615
+ == "url"
616
+ ):
617
+ kwargs.update(
618
+ {
619
+ "file_url": element.get(
620
+ "source",
621
+ {},
622
+ ).get("url"),
623
+ },
624
+ )
625
+
626
+ elif (
627
+ isinstance(element.get("source"), dict)
628
+ and element.get("source").get(
629
+ "type",
630
+ )
631
+ == "base64"
632
+ ):
633
+ media_type = element.get("source", {}).get(
634
+ "media_type",
635
+ "application/octet-stream",
636
+ )
637
+ base64_data = element.get("source", {}).get(
638
+ "data",
639
+ "",
640
+ )
641
+ url = f"data:{media_type};base64,{base64_data}"
642
+ kwargs.update({"file_url": url})
643
+ delta_content = FileContent(
644
+ delta=True,
645
+ index=index,
646
+ **kwargs,
647
+ )
518
648
  else:
519
649
  delta_content = TextContent(
520
650
  delta=True,
@@ -1,5 +1,5 @@
1
1
  # -*- coding: utf-8 -*-
2
- from typing import Union, List
2
+ from typing import Union, List, Callable, Optional, Dict
3
3
 
4
4
  from agentscope.formatter import OpenAIChatFormatter
5
5
 
@@ -9,18 +9,27 @@ from ..agentscope.message import message_to_agentscope_msg
9
9
 
10
10
  async def message_to_agno_message(
11
11
  messages: Union[Message, List[Message]],
12
+ type_converters: Optional[Dict[str, Callable]] = None,
12
13
  ) -> Union[dict, List[dict]]:
13
14
  """
14
15
  Convert AgentScope runtime Message(s) to Agno Message(s).
15
16
 
16
17
  Args:
17
18
  messages: A single AgentScope runtime Message or list of Messages.
19
+ type_converters: Optional mapping from ``message.type`` to a callable
20
+ ``converter(message)``. When provided and the current
21
+ ``message.type`` exists in the mapping, the corresponding converter
22
+ will be used and the built-in conversion logic will be skipped for
23
+ that message.
18
24
 
19
25
  Returns:
20
26
  A single AgnoMessage object or a list of AgnoMessage objects.
21
27
  """
22
28
 
23
- as_msgs = message_to_agentscope_msg(messages)
29
+ as_msgs = message_to_agentscope_msg(
30
+ messages,
31
+ type_converters=type_converters,
32
+ )
24
33
  raw_list = isinstance(as_msgs, list)
25
34
  as_msgs = as_msgs if raw_list else [as_msgs]
26
35
 
@@ -31,6 +31,7 @@ from ...engine.helpers.agent_api_builder import ResponseBuilder
31
31
 
32
32
  async def adapt_agno_message_stream(
33
33
  source_stream: AsyncIterator[BaseAgentRunEvent],
34
+ **kwargs, # pylint:disable=unused-argument
34
35
  ) -> AsyncIterator[Union[Message, Content]]:
35
36
  rb = ResponseBuilder()
36
37
  mb = None
@@ -2,11 +2,9 @@
2
2
  """LangGraph adapter for AgentScope runtime."""
3
3
 
4
4
  # todo Message(reasoning) Adapter
5
- # todo Memory Adapter
6
5
  # todo Sandbox Tools Adapter
7
- from .message import langgraph_msg_to_message, message_to_langgraph_msg
6
+ from .message import message_to_langgraph_msg
8
7
 
9
8
  __all__ = [
10
- "langgraph_msg_to_message",
11
9
  "message_to_langgraph_msg",
12
10
  ]