agentscope-runtime 1.0.3__py3-none-any.whl → 1.0.4__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 (49) hide show
  1. agentscope_runtime/adapters/agentscope/stream.py +2 -9
  2. agentscope_runtime/adapters/ms_agent_framework/__init__.py +0 -0
  3. agentscope_runtime/adapters/ms_agent_framework/message.py +205 -0
  4. agentscope_runtime/adapters/ms_agent_framework/stream.py +418 -0
  5. agentscope_runtime/adapters/utils.py +6 -0
  6. agentscope_runtime/cli/commands/deploy.py +371 -0
  7. agentscope_runtime/common/container_clients/knative_client.py +466 -0
  8. agentscope_runtime/engine/__init__.py +4 -0
  9. agentscope_runtime/engine/constant.py +1 -0
  10. agentscope_runtime/engine/deployers/__init__.py +12 -0
  11. agentscope_runtime/engine/deployers/adapter/a2a/__init__.py +26 -51
  12. agentscope_runtime/engine/deployers/adapter/a2a/a2a_protocol_adapter.py +19 -10
  13. agentscope_runtime/engine/deployers/adapter/a2a/a2a_registry.py +4 -201
  14. agentscope_runtime/engine/deployers/adapter/a2a/nacos_a2a_registry.py +134 -25
  15. agentscope_runtime/engine/deployers/agentrun_deployer.py +2 -2
  16. agentscope_runtime/engine/deployers/fc_deployer.py +1506 -0
  17. agentscope_runtime/engine/deployers/knative_deployer.py +290 -0
  18. agentscope_runtime/engine/runner.py +12 -0
  19. agentscope_runtime/engine/services/agent_state/redis_state_service.py +2 -2
  20. agentscope_runtime/engine/services/memory/redis_memory_service.py +2 -2
  21. agentscope_runtime/engine/services/session_history/redis_session_history_service.py +2 -2
  22. agentscope_runtime/engine/tracing/wrapper.py +18 -4
  23. agentscope_runtime/sandbox/__init__.py +14 -6
  24. agentscope_runtime/sandbox/box/base/__init__.py +2 -2
  25. agentscope_runtime/sandbox/box/base/base_sandbox.py +51 -1
  26. agentscope_runtime/sandbox/box/browser/__init__.py +2 -2
  27. agentscope_runtime/sandbox/box/browser/browser_sandbox.py +198 -2
  28. agentscope_runtime/sandbox/box/filesystem/__init__.py +2 -2
  29. agentscope_runtime/sandbox/box/filesystem/filesystem_sandbox.py +99 -2
  30. agentscope_runtime/sandbox/box/gui/__init__.py +2 -2
  31. agentscope_runtime/sandbox/box/gui/gui_sandbox.py +117 -1
  32. agentscope_runtime/sandbox/box/mobile/__init__.py +2 -2
  33. agentscope_runtime/sandbox/box/mobile/mobile_sandbox.py +247 -100
  34. agentscope_runtime/sandbox/box/sandbox.py +98 -65
  35. agentscope_runtime/sandbox/box/shared/routers/generic.py +36 -29
  36. agentscope_runtime/sandbox/client/__init__.py +6 -1
  37. agentscope_runtime/sandbox/client/async_http_client.py +339 -0
  38. agentscope_runtime/sandbox/client/base.py +74 -0
  39. agentscope_runtime/sandbox/client/http_client.py +108 -329
  40. agentscope_runtime/sandbox/enums.py +7 -0
  41. agentscope_runtime/sandbox/manager/sandbox_manager.py +264 -4
  42. agentscope_runtime/sandbox/manager/server/app.py +7 -1
  43. agentscope_runtime/version.py +1 -1
  44. {agentscope_runtime-1.0.3.dist-info → agentscope_runtime-1.0.4.dist-info}/METADATA +102 -28
  45. {agentscope_runtime-1.0.3.dist-info → agentscope_runtime-1.0.4.dist-info}/RECORD +49 -40
  46. {agentscope_runtime-1.0.3.dist-info → agentscope_runtime-1.0.4.dist-info}/WHEEL +0 -0
  47. {agentscope_runtime-1.0.3.dist-info → agentscope_runtime-1.0.4.dist-info}/entry_points.txt +0 -0
  48. {agentscope_runtime-1.0.3.dist-info → agentscope_runtime-1.0.4.dist-info}/licenses/LICENSE +0 -0
  49. {agentscope_runtime-1.0.3.dist-info → agentscope_runtime-1.0.4.dist-info}/top_level.txt +0 -0
@@ -6,8 +6,8 @@ from urllib.parse import urlparse, urlunparse
6
6
  from ...utils import build_image_uri
7
7
  from ...registry import SandboxRegistry
8
8
  from ...enums import SandboxType
9
- from ...box.base import BaseSandbox
10
- from ...box.gui import GUIMixin
9
+ from ...box.base import BaseSandbox, BaseSandboxAsync
10
+ from ...box.gui import GUIMixin, AsyncGUIMixin
11
11
  from ...constant import TIMEOUT
12
12
 
13
13
 
@@ -299,3 +299,199 @@ class BrowserSandbox(GUIMixin, BaseSandbox):
299
299
  "textGone": text_gone,
300
300
  },
301
301
  )
302
+
303
+
304
+ @SandboxRegistry.register(
305
+ build_image_uri("runtime-sandbox-browser"),
306
+ sandbox_type=SandboxType.BROWSER_ASYNC,
307
+ security_level="medium",
308
+ timeout=TIMEOUT,
309
+ description="Browser sandbox (Async)",
310
+ )
311
+ class BrowserSandboxAsync(GUIMixin, AsyncGUIMixin, BaseSandboxAsync):
312
+ def __init__( # pylint: disable=useless-parent-delegation
313
+ self,
314
+ sandbox_id: Optional[str] = None,
315
+ timeout: int = 3000,
316
+ base_url: Optional[str] = None,
317
+ bearer_token: Optional[str] = None,
318
+ sandbox_type: SandboxType = SandboxType.BROWSER_ASYNC,
319
+ ):
320
+ super().__init__(
321
+ sandbox_id,
322
+ timeout,
323
+ base_url,
324
+ bearer_token,
325
+ sandbox_type,
326
+ )
327
+
328
+ async def browser_close(self):
329
+ """Close the current browser page."""
330
+ return await self.call_tool_async("browser_close", {})
331
+
332
+ async def browser_resize(self, width: int, height: int):
333
+ """Resize the browser window."""
334
+ return await self.call_tool_async(
335
+ "browser_resize",
336
+ {"width": width, "height": height},
337
+ )
338
+
339
+ async def browser_console_messages(self):
340
+ """Return all console messages from the browser."""
341
+ return await self.call_tool_async("browser_console_messages", {})
342
+
343
+ async def browser_handle_dialog(self, accept: bool, prompt_text: str = ""):
344
+ """Handle a dialog popup."""
345
+ return await self.call_tool_async(
346
+ "browser_handle_dialog",
347
+ {"accept": accept, "promptText": prompt_text},
348
+ )
349
+
350
+ async def browser_file_upload(self, paths: list):
351
+ """Upload one or multiple files."""
352
+ return await self.call_tool_async(
353
+ "browser_file_upload",
354
+ {"paths": paths},
355
+ )
356
+
357
+ async def browser_press_key(self, key: str):
358
+ """Press a key in the browser."""
359
+ return await self.call_tool_async("browser_press_key", {"key": key})
360
+
361
+ async def browser_navigate(self, url: str):
362
+ """Navigate to a URL."""
363
+ return await self.call_tool_async("browser_navigate", {"url": url})
364
+
365
+ async def browser_navigate_back(self):
366
+ """Go back in browser history."""
367
+ return await self.call_tool_async("browser_navigate_back", {})
368
+
369
+ async def browser_navigate_forward(self):
370
+ """Go forward in browser history."""
371
+ return await self.call_tool_async("browser_navigate_forward", {})
372
+
373
+ async def browser_network_requests(self):
374
+ """Return network requests."""
375
+ return await self.call_tool_async("browser_network_requests", {})
376
+
377
+ async def browser_pdf_save(self, filename: str = ""):
378
+ """Save page as a PDF."""
379
+ return await self.call_tool_async(
380
+ "browser_pdf_save",
381
+ {"filename": filename},
382
+ )
383
+
384
+ async def browser_take_screenshot(
385
+ self,
386
+ raw=False,
387
+ filename="",
388
+ element="",
389
+ ref="",
390
+ ):
391
+ """Take a screenshot."""
392
+ return await self.call_tool_async(
393
+ "browser_take_screenshot",
394
+ {"raw": raw, "filename": filename, "element": element, "ref": ref},
395
+ )
396
+
397
+ async def browser_snapshot(self):
398
+ """Accessibility snapshot."""
399
+ return await self.call_tool_async("browser_snapshot", {})
400
+
401
+ async def browser_click(self, element: str, ref: str):
402
+ """Click an element."""
403
+ return await self.call_tool_async(
404
+ "browser_click",
405
+ {"element": element, "ref": ref},
406
+ )
407
+
408
+ async def browser_drag(
409
+ self,
410
+ start_element: str,
411
+ start_ref: str,
412
+ end_element: str,
413
+ end_ref: str,
414
+ ):
415
+ """Drag and drop."""
416
+ return await self.call_tool_async(
417
+ "browser_drag",
418
+ {
419
+ "startElement": start_element,
420
+ "startRef": start_ref,
421
+ "endElement": end_element,
422
+ "endRef": end_ref,
423
+ },
424
+ )
425
+
426
+ async def browser_hover(self, element: str, ref: str):
427
+ """Hover over an element."""
428
+ return await self.call_tool_async(
429
+ "browser_hover",
430
+ {"element": element, "ref": ref},
431
+ )
432
+
433
+ async def browser_type(
434
+ self,
435
+ element: str,
436
+ ref: str,
437
+ text: str,
438
+ submit=False,
439
+ slowly=False,
440
+ ):
441
+ """Type text into an element."""
442
+ return await self.call_tool_async(
443
+ "browser_type",
444
+ {
445
+ "element": element,
446
+ "ref": ref,
447
+ "text": text,
448
+ "submit": submit,
449
+ "slowly": slowly,
450
+ },
451
+ )
452
+
453
+ async def browser_select_option(
454
+ self,
455
+ element: str,
456
+ ref: str,
457
+ values: list,
458
+ ):
459
+ """Select options in a dropdown."""
460
+ return await self.call_tool_async(
461
+ "browser_select_option",
462
+ {"element": element, "ref": ref, "values": values},
463
+ )
464
+
465
+ async def browser_tab_list(self):
466
+ """List all tabs."""
467
+ return await self.call_tool_async("browser_tab_list", {})
468
+
469
+ async def browser_tab_new(self, url: str = ""):
470
+ """Open a new tab."""
471
+ return await self.call_tool_async("browser_tab_new", {"url": url})
472
+
473
+ async def browser_tab_select(self, index: int):
474
+ """Select tab by index."""
475
+ return await self.call_tool_async(
476
+ "browser_tab_select",
477
+ {"index": index},
478
+ )
479
+
480
+ async def browser_tab_close(self, index: int = None):
481
+ """Close a tab."""
482
+ return await self.call_tool_async(
483
+ "browser_tab_close",
484
+ {"index": index},
485
+ )
486
+
487
+ async def browser_wait_for(
488
+ self,
489
+ time: float = None,
490
+ text: str = None,
491
+ text_gone: str = None,
492
+ ):
493
+ """Wait for text or time."""
494
+ return await self.call_tool_async(
495
+ "browser_wait_for",
496
+ {"time": time, "text": text, "textGone": text_gone},
497
+ )
@@ -1,4 +1,4 @@
1
1
  # -*- coding: utf-8 -*-
2
- from .filesystem_sandbox import FilesystemSandbox
2
+ from .filesystem_sandbox import FilesystemSandbox, FilesystemSandboxAsync
3
3
 
4
- __all__ = ["FilesystemSandbox"]
4
+ __all__ = ["FilesystemSandbox", "FilesystemSandboxAsync"]
@@ -5,8 +5,8 @@ from typing import Optional
5
5
  from ...utils import build_image_uri
6
6
  from ...registry import SandboxRegistry
7
7
  from ...enums import SandboxType
8
- from ...box.base import BaseSandbox
9
- from ...box.gui import GUIMixin
8
+ from ...box.base import BaseSandbox, BaseSandboxAsync
9
+ from ...box.gui import GUIMixin, AsyncGUIMixin
10
10
  from ...constant import TIMEOUT
11
11
 
12
12
 
@@ -154,3 +154,100 @@ class FilesystemSandbox(GUIMixin, BaseSandbox):
154
154
  Returns the list of directories that this serveris allowed to access.
155
155
  """
156
156
  return self.call_tool("list_allowed_directories", {})
157
+
158
+
159
+ @SandboxRegistry.register(
160
+ build_image_uri("runtime-sandbox-filesystem"),
161
+ sandbox_type=SandboxType.FILESYSTEM_ASYNC,
162
+ security_level="medium",
163
+ timeout=TIMEOUT,
164
+ description="Filesystem sandbox (Async)",
165
+ )
166
+ class FilesystemSandboxAsync(GUIMixin, AsyncGUIMixin, BaseSandboxAsync):
167
+ def __init__( # pylint: disable=useless-parent-delegation
168
+ self,
169
+ sandbox_id: Optional[str] = None,
170
+ timeout: int = 3000,
171
+ base_url: Optional[str] = None,
172
+ bearer_token: Optional[str] = None,
173
+ sandbox_type: SandboxType = SandboxType.FILESYSTEM_ASYNC,
174
+ ):
175
+ super().__init__(
176
+ sandbox_id,
177
+ timeout,
178
+ base_url,
179
+ bearer_token,
180
+ sandbox_type,
181
+ )
182
+
183
+ async def read_file(self, path: str):
184
+ """Read the complete contents of a file."""
185
+ return await self.call_tool_async("read_file", {"path": path})
186
+
187
+ async def read_multiple_files(self, paths: list):
188
+ """Read the contents of multiple files simultaneously."""
189
+ return await self.call_tool_async(
190
+ "read_multiple_files",
191
+ {"paths": paths},
192
+ )
193
+
194
+ async def write_file(self, path: str, content: str):
195
+ """Create or overwrite a file with new content."""
196
+ return await self.call_tool_async(
197
+ "write_file",
198
+ {"path": path, "content": content},
199
+ )
200
+
201
+ async def edit_file(self, path: str, edits: list, dry_run: bool = False):
202
+ """Make line-based edits to a text file."""
203
+ return await self.call_tool_async(
204
+ "edit_file",
205
+ {
206
+ "path": path,
207
+ "edits": edits,
208
+ "dryRun": dry_run,
209
+ },
210
+ )
211
+
212
+ async def create_directory(self, path: str):
213
+ """Create a new directory or ensure it exists."""
214
+ return await self.call_tool_async("create_directory", {"path": path})
215
+
216
+ async def list_directory(self, path: str):
217
+ """Get a detailed listing of all files and directories."""
218
+ return await self.call_tool_async("list_directory", {"path": path})
219
+
220
+ async def directory_tree(self, path: str):
221
+ """Get a recursive tree view of files and directories as JSON."""
222
+ return await self.call_tool_async("directory_tree", {"path": path})
223
+
224
+ async def move_file(self, source: str, destination: str):
225
+ """Move or rename files and directories."""
226
+ return await self.call_tool_async(
227
+ "move_file",
228
+ {"source": source, "destination": destination},
229
+ )
230
+
231
+ async def search_files(
232
+ self,
233
+ path: str,
234
+ pattern: str,
235
+ exclude_patterns: list = [],
236
+ ):
237
+ """Recursively search for files and directories matching a pattern."""
238
+ return await self.call_tool_async(
239
+ "search_files",
240
+ {
241
+ "path": path,
242
+ "pattern": pattern,
243
+ "excludePatterns": exclude_patterns,
244
+ },
245
+ )
246
+
247
+ async def get_file_info(self, path: str):
248
+ """Retrieve metadata about a file or directory."""
249
+ return await self.call_tool_async("get_file_info", {"path": path})
250
+
251
+ async def list_allowed_directories(self):
252
+ """Returns directories this server can access."""
253
+ return await self.call_tool_async("list_allowed_directories", {})
@@ -1,4 +1,4 @@
1
1
  # -*- coding: utf-8 -*-
2
- from .gui_sandbox import GuiSandbox, GUIMixin
2
+ from .gui_sandbox import GuiSandbox, GuiSandboxAsync, GUIMixin, AsyncGUIMixin
3
3
 
4
- __all__ = ["GuiSandbox", "GUIMixin"]
4
+ __all__ = ["GuiSandbox", "GuiSandboxAsync", "GUIMixin", "AsyncGUIMixin"]
@@ -8,7 +8,7 @@ from urllib.parse import urljoin, urlencode
8
8
  from ...utils import build_image_uri, get_platform
9
9
  from ...registry import SandboxRegistry
10
10
  from ...enums import SandboxType
11
- from ...box.base import BaseSandbox
11
+ from ...box.base import BaseSandbox, BaseSandboxAsync
12
12
  from ...constant import TIMEOUT
13
13
 
14
14
  logger = logging.getLogger(__name__)
@@ -34,6 +34,34 @@ class GUIMixin:
34
34
  )
35
35
 
36
36
 
37
+ class AsyncGUIMixin:
38
+ async def get_desktop_url_async(self):
39
+ # Check sandbox health asynchronously
40
+ is_healthy = await self.manager_api.check_health_async(
41
+ identity=self.sandbox_id,
42
+ )
43
+ if not is_healthy:
44
+ raise RuntimeError(f"Sandbox {self.sandbox_id} is not healthy")
45
+
46
+ # Retrieve container information asynchronously
47
+ info = await self.get_info_async()
48
+
49
+ # Default local VNC path and remote VNC relay path
50
+ path = "/vnc/vnc_lite.html"
51
+ remote_path = "/vnc/vnc_relay.html"
52
+ params = {"password": info["runtime_token"]}
53
+
54
+ # If base_url is not set, construct the local URL
55
+ if self.base_url is None:
56
+ return urljoin(info["url"], path) + "?" + urlencode(params)
57
+
58
+ # Construct the remote URL with sandbox ID and VNC relay path
59
+ return (
60
+ f"{self.base_url}/desktop/{self.sandbox_id}{remote_path}"
61
+ f"?{urlencode(params)}"
62
+ )
63
+
64
+
37
65
  @SandboxRegistry.register(
38
66
  build_image_uri("runtime-sandbox-gui"),
39
67
  sandbox_type=SandboxType.GUI,
@@ -121,3 +149,91 @@ class GuiSandbox(GUIMixin, BaseSandbox):
121
149
  payload["text"] = text
122
150
 
123
151
  return self.call_tool("computer", payload)
152
+
153
+
154
+ @SandboxRegistry.register(
155
+ build_image_uri("runtime-sandbox-gui"),
156
+ sandbox_type=SandboxType.GUI_ASYNC,
157
+ security_level="high",
158
+ timeout=TIMEOUT,
159
+ description="GUI Sandbox (Async)",
160
+ )
161
+ class GuiSandboxAsync(GUIMixin, AsyncGUIMixin, BaseSandboxAsync):
162
+ def __init__( # pylint: disable=useless-parent-delegation
163
+ self,
164
+ sandbox_id: Optional[str] = None,
165
+ timeout: int = 3000,
166
+ base_url: Optional[str] = None,
167
+ bearer_token: Optional[str] = None,
168
+ sandbox_type: SandboxType = SandboxType.GUI_ASYNC,
169
+ ):
170
+ super().__init__(
171
+ sandbox_id,
172
+ timeout,
173
+ base_url,
174
+ bearer_token,
175
+ sandbox_type,
176
+ )
177
+ # Architecture compatibility warning
178
+ if get_platform() == "linux/arm64":
179
+ logger.warning(
180
+ "\nCompatibility Notice: This GUI Sandbox may have issues on "
181
+ "arm64 CPU architectures, due to the computer-use-mcp not "
182
+ "providing linux/arm64 compatibility. It has been tested "
183
+ "on Apple M4 chips with Rosetta enabled. However, on M1, M2, "
184
+ "and M3 chips, Chromium browser might crash due to the missing "
185
+ "SSE3 instruction set.",
186
+ )
187
+
188
+ async def computer_use(
189
+ self,
190
+ action: str,
191
+ coordinate: Optional[Union[List[float], Tuple[float, float]]] = None,
192
+ text: Optional[str] = None,
193
+ ):
194
+ """
195
+ Asynchronously use mouse and keyboard to interact with a desktop GUI.
196
+
197
+ This method interfaces with the sandbox's GUI environment.
198
+ You do not have access to a terminal or applications menu;
199
+ interaction is performed by clicking on desktop icons or using
200
+ keyboard shortcuts.
201
+
202
+ Guidelines:
203
+ * Prefer keyboard shortcuts where possible over cursor actions.
204
+ * If visual keyboard hints (two-letter boxes) are shown, typing
205
+ those letters will click the element — use this where possible.
206
+ * Applications or actions may require waiting; e.g., repeat
207
+ screenshots if windows don’t open immediately.
208
+ * Always determine cursor coordinates using screenshots before moving
209
+ the cursor to click on elements.
210
+ * If clicks fail to load content, try adjusting cursor coordinates to
211
+ center on the target element.
212
+ * Click with the cursor tip centered on elements, not on edges.
213
+
214
+ Args:
215
+ action (str): The action to perform. Options include:
216
+ * `key` — Press a key or key combination.
217
+ * `type` — Type a string of text.
218
+ * `get_cursor_position` — Get current cursor coordinates (x, y).
219
+ * `mouse_move` — Move cursor to given (x, y).
220
+ * `left_click` — Left mouse click.
221
+ * `left_click_drag` — Click and drag to given (x, y).
222
+ * `right_click` — Right mouse click.
223
+ * `middle_click` — Middle mouse click.
224
+ * `double_click` — Double left mouse click.
225
+ * `get_screenshot` — Capture screen screenshot.
226
+ coordinate (list[float] | tuple[float, float], optional):
227
+ Pixel coordinates (x from left edge, y from top edge).
228
+ text (str, optional): String to type, or key-combination for `key` action.
229
+
230
+ Returns:
231
+ Any: Tool execution result from the sandbox.
232
+ """
233
+ payload = {"action": action}
234
+ if coordinate is not None:
235
+ payload["coordinate"] = coordinate
236
+ if text is not None:
237
+ payload["text"] = text
238
+
239
+ return await self.call_tool_async("computer", payload)
@@ -1,4 +1,4 @@
1
1
  # -*- coding: utf-8 -*-
2
- from .mobile_sandbox import MobileSandbox
2
+ from .mobile_sandbox import MobileSandbox, MobileSandboxAsync
3
3
 
4
- __all__ = ["MobileSandbox"]
4
+ __all__ = ["MobileSandbox", "MobileSandboxAsync"]