agentscope-runtime 1.0.4a1__py3-none-any.whl → 1.0.5__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 (79) hide show
  1. agentscope_runtime/adapters/agentscope/stream.py +2 -8
  2. agentscope_runtime/adapters/langgraph/stream.py +120 -70
  3. agentscope_runtime/adapters/ms_agent_framework/__init__.py +0 -0
  4. agentscope_runtime/adapters/ms_agent_framework/message.py +205 -0
  5. agentscope_runtime/adapters/ms_agent_framework/stream.py +418 -0
  6. agentscope_runtime/adapters/utils.py +6 -0
  7. agentscope_runtime/cli/commands/deploy.py +836 -1
  8. agentscope_runtime/cli/commands/stop.py +16 -0
  9. agentscope_runtime/common/container_clients/__init__.py +52 -0
  10. agentscope_runtime/common/container_clients/agentrun_client.py +6 -4
  11. agentscope_runtime/common/container_clients/boxlite_client.py +442 -0
  12. agentscope_runtime/common/container_clients/docker_client.py +0 -20
  13. agentscope_runtime/common/container_clients/fc_client.py +6 -4
  14. agentscope_runtime/common/container_clients/gvisor_client.py +38 -0
  15. agentscope_runtime/common/container_clients/knative_client.py +467 -0
  16. agentscope_runtime/common/utils/deprecation.py +164 -0
  17. agentscope_runtime/engine/__init__.py +4 -0
  18. agentscope_runtime/engine/app/agent_app.py +16 -4
  19. agentscope_runtime/engine/constant.py +1 -0
  20. agentscope_runtime/engine/deployers/__init__.py +34 -11
  21. agentscope_runtime/engine/deployers/adapter/__init__.py +8 -0
  22. agentscope_runtime/engine/deployers/adapter/a2a/__init__.py +26 -51
  23. agentscope_runtime/engine/deployers/adapter/a2a/a2a_protocol_adapter.py +23 -13
  24. agentscope_runtime/engine/deployers/adapter/a2a/a2a_registry.py +4 -201
  25. agentscope_runtime/engine/deployers/adapter/a2a/nacos_a2a_registry.py +152 -25
  26. agentscope_runtime/engine/deployers/adapter/agui/__init__.py +8 -0
  27. agentscope_runtime/engine/deployers/adapter/agui/agui_adapter_utils.py +652 -0
  28. agentscope_runtime/engine/deployers/adapter/agui/agui_protocol_adapter.py +225 -0
  29. agentscope_runtime/engine/deployers/agentrun_deployer.py +2 -2
  30. agentscope_runtime/engine/deployers/fc_deployer.py +1506 -0
  31. agentscope_runtime/engine/deployers/knative_deployer.py +290 -0
  32. agentscope_runtime/engine/deployers/pai_deployer.py +2335 -0
  33. agentscope_runtime/engine/deployers/utils/net_utils.py +37 -0
  34. agentscope_runtime/engine/deployers/utils/oss_utils.py +38 -0
  35. agentscope_runtime/engine/deployers/utils/package.py +46 -42
  36. agentscope_runtime/engine/helpers/agent_api_client.py +372 -0
  37. agentscope_runtime/engine/runner.py +13 -0
  38. agentscope_runtime/engine/schemas/agent_schemas.py +9 -3
  39. agentscope_runtime/engine/services/agent_state/__init__.py +7 -0
  40. agentscope_runtime/engine/services/memory/__init__.py +7 -0
  41. agentscope_runtime/engine/services/memory/redis_memory_service.py +15 -16
  42. agentscope_runtime/engine/services/session_history/__init__.py +7 -0
  43. agentscope_runtime/engine/tracing/local_logging_handler.py +2 -3
  44. agentscope_runtime/engine/tracing/wrapper.py +18 -4
  45. agentscope_runtime/sandbox/__init__.py +14 -6
  46. agentscope_runtime/sandbox/box/base/__init__.py +2 -2
  47. agentscope_runtime/sandbox/box/base/base_sandbox.py +51 -1
  48. agentscope_runtime/sandbox/box/browser/__init__.py +2 -2
  49. agentscope_runtime/sandbox/box/browser/browser_sandbox.py +198 -2
  50. agentscope_runtime/sandbox/box/filesystem/__init__.py +2 -2
  51. agentscope_runtime/sandbox/box/filesystem/filesystem_sandbox.py +99 -2
  52. agentscope_runtime/sandbox/box/gui/__init__.py +2 -2
  53. agentscope_runtime/sandbox/box/gui/gui_sandbox.py +117 -1
  54. agentscope_runtime/sandbox/box/mobile/__init__.py +2 -2
  55. agentscope_runtime/sandbox/box/mobile/mobile_sandbox.py +247 -100
  56. agentscope_runtime/sandbox/box/sandbox.py +102 -65
  57. agentscope_runtime/sandbox/box/shared/routers/generic.py +36 -29
  58. agentscope_runtime/sandbox/client/__init__.py +6 -1
  59. agentscope_runtime/sandbox/client/async_http_client.py +339 -0
  60. agentscope_runtime/sandbox/client/base.py +74 -0
  61. agentscope_runtime/sandbox/client/http_client.py +108 -329
  62. agentscope_runtime/sandbox/enums.py +7 -0
  63. agentscope_runtime/sandbox/manager/sandbox_manager.py +275 -29
  64. agentscope_runtime/sandbox/manager/server/app.py +7 -1
  65. agentscope_runtime/sandbox/manager/server/config.py +3 -1
  66. agentscope_runtime/sandbox/model/manager_config.py +11 -9
  67. agentscope_runtime/tools/modelstudio_memory/__init__.py +106 -0
  68. agentscope_runtime/tools/modelstudio_memory/base.py +220 -0
  69. agentscope_runtime/tools/modelstudio_memory/config.py +86 -0
  70. agentscope_runtime/tools/modelstudio_memory/core.py +594 -0
  71. agentscope_runtime/tools/modelstudio_memory/exceptions.py +60 -0
  72. agentscope_runtime/tools/modelstudio_memory/schemas.py +253 -0
  73. agentscope_runtime/version.py +1 -1
  74. {agentscope_runtime-1.0.4a1.dist-info → agentscope_runtime-1.0.5.dist-info}/METADATA +186 -73
  75. {agentscope_runtime-1.0.4a1.dist-info → agentscope_runtime-1.0.5.dist-info}/RECORD +79 -55
  76. {agentscope_runtime-1.0.4a1.dist-info → agentscope_runtime-1.0.5.dist-info}/WHEEL +0 -0
  77. {agentscope_runtime-1.0.4a1.dist-info → agentscope_runtime-1.0.5.dist-info}/entry_points.txt +0 -0
  78. {agentscope_runtime-1.0.4a1.dist-info → agentscope_runtime-1.0.5.dist-info}/licenses/LICENSE +0 -0
  79. {agentscope_runtime-1.0.4a1.dist-info → agentscope_runtime-1.0.5.dist-info}/top_level.txt +0 -0
@@ -61,6 +61,22 @@ def _create_deployer(
61
61
  )
62
62
 
63
63
  return AgentRunDeployManager()
64
+
65
+ elif platform == "pai":
66
+ from agentscope_runtime.engine.deployers.pai_deployer import (
67
+ PAIDeployManager,
68
+ )
69
+
70
+ # Extract workspace_id from deployment config
71
+ config = deployment_state.get("config", {})
72
+ workspace_id = config.get("workspace_id")
73
+ region = config.get("region_id")
74
+ oss_path = config.get("oss_path")
75
+ return PAIDeployManager(
76
+ workspace_id=workspace_id,
77
+ region_id=region,
78
+ oss_path=oss_path,
79
+ )
64
80
  else:
65
81
  echo_warning(f"Unknown platform: {platform}")
66
82
  return None
@@ -0,0 +1,52 @@
1
+ # -*- coding: utf-8 -*-
2
+ import sys
3
+ from typing import TYPE_CHECKING
4
+
5
+ from ..utils.lazy_loader import install_lazy_loader
6
+
7
+ if TYPE_CHECKING:
8
+ from .docker_client import DockerClient
9
+ from .kubernetes_client import KubernetesClient
10
+ from .knative_client import KnativeClient
11
+ from .fc_client import FCClient
12
+ from .agentrun_client import AgentRunClient
13
+ from .gvisor_client import GVisorDockerClient
14
+ from .boxlite_client import BoxliteClient
15
+
16
+ install_lazy_loader(
17
+ globals(),
18
+ {
19
+ "DockerClient": ".docker_client",
20
+ "KubernetesClient": ".kubernetes_client",
21
+ "KnativeClient": ".knative_client",
22
+ "FCClient": ".fc_client",
23
+ "AgentRunClient": ".agentrun_client",
24
+ "GVisorDockerClient": ".gvisor_client",
25
+ "BoxliteClient": ".boxlite_client",
26
+ },
27
+ )
28
+
29
+
30
+ class ContainerClientFactory:
31
+ _CLIENT_MAPPING = {
32
+ "docker": "DockerClient",
33
+ "k8s": "KubernetesClient",
34
+ "knative": "KnativeClient",
35
+ "fc": "FCClient",
36
+ "agentrun": "AgentRunClient",
37
+ "gvisor": "GVisorDockerClient",
38
+ "boxlite": "BoxliteClient",
39
+ }
40
+
41
+ @classmethod
42
+ def create_client(cls, deployment_type, config):
43
+ try:
44
+ class_name = cls._CLIENT_MAPPING[deployment_type]
45
+ except KeyError as e:
46
+ raise NotImplementedError(
47
+ f"Container deployment '{deployment_type}' not implemented",
48
+ ) from e
49
+
50
+ module = sys.modules[__name__]
51
+ client_class = getattr(module, class_name)
52
+ return client_class(config=config)
@@ -1078,13 +1078,15 @@ class AgentRunClient(BaseClient):
1078
1078
  """
1079
1079
  replacement_map = {
1080
1080
  "agentscope/runtime-sandbox-base": "serverless-registry.cn-hangzhou.cr.aliyuncs.com/functionai" # noqa: E501
1081
- "/agentscope_runtime-sandbox-base:20251027",
1081
+ "/agentscope_runtime-sandbox-base:20260106",
1082
1082
  "agentscope/runtime-sandbox-browser": "serverless-registry.cn-hangzhou.cr.aliyuncs.com/functionai" # noqa: E501
1083
- "/agentscope_runtime-sandbox-browser:20251027",
1083
+ "/agentscope_runtime-sandbox-browser:20260106",
1084
1084
  "agentscope/runtime-sandbox-filesystem": "serverless-registry.cn-hangzhou.cr.aliyuncs.com/functionai" # noqa: E501
1085
- "/agentscope_runtime-sandbox-filesystem:20251027",
1085
+ "/agentscope_runtime-sandbox-filesystem:20260106",
1086
1086
  "agentscope/runtime-sandbox-gui": "serverless-registry.cn-hangzhou.cr.aliyuncs.com/functionai" # noqa: E501
1087
- "/agentscope_runtime-sandbox-gui:20251027",
1087
+ "/agentscope_runtime-sandbox-gui:20260106",
1088
+ "agentscope/runtime-sandbox-mobile": "serverless-registry.cn-hangzhou.cr.aliyuncs.com/functionai" # noqa: E501
1089
+ "/agentscope_runtime-sandbox-mobile:20251217",
1088
1090
  }
1089
1091
 
1090
1092
  if ":" in image:
@@ -0,0 +1,442 @@
1
+ # -*- coding: utf-8 -*-
2
+ # pylint: disable=too-many-branches
3
+ import atexit
4
+ import logging
5
+ import socket
6
+ import traceback
7
+
8
+ import boxlite
9
+ from boxlite import SyncBoxlite
10
+
11
+ from .base_client import BaseClient
12
+ from ..collections import (
13
+ RedisSetCollection,
14
+ InMemorySetCollection,
15
+ RedisMapping,
16
+ InMemoryMapping,
17
+ )
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ class BoxliteClient(BaseClient):
23
+ """
24
+ BoxLite client implementation that provides a Docker-like interface
25
+ for managing boxes using the BoxLite SDK.
26
+ """
27
+
28
+ def __init__(self, config=None):
29
+ """
30
+ Initialize the BoxLite client.
31
+
32
+ Args:
33
+ config: Configuration object with optional attributes:
34
+ - port_range: Tuple of (start, end) for port range
35
+ (default: (8000, 9000))
36
+ - redis_enabled: Whether to use Redis for port management
37
+ (default: False)
38
+ - redis_server: Redis server host (default: 'localhost')
39
+ - redis_port: Redis server port (default: 6379)
40
+ - redis_db: Redis database number (default: 0)
41
+ - redis_user: Redis username (optional)
42
+ - redis_password: Redis password (optional)
43
+ - redis_port_key: Redis key prefix for ports
44
+ (default: 'boxlite:ports')
45
+ """
46
+ self.config = config
47
+
48
+ self.port_range = range(*self.config.port_range)
49
+
50
+ # Initialize port management
51
+ if hasattr(self.config, "redis_enabled") and self.config.redis_enabled:
52
+ import redis
53
+
54
+ redis_client = redis.Redis(
55
+ host=getattr(self.config, "redis_server", "localhost"),
56
+ port=getattr(self.config, "redis_port", 6379),
57
+ db=getattr(self.config, "redis_db", 0),
58
+ username=getattr(self.config, "redis_user", None),
59
+ password=getattr(self.config, "redis_password", None),
60
+ decode_responses=True,
61
+ )
62
+ try:
63
+ redis_client.ping()
64
+ except ConnectionError as e:
65
+ raise RuntimeError(
66
+ "Unable to connect to the Redis server.",
67
+ ) from e
68
+
69
+ self.port_set = RedisSetCollection(
70
+ redis_client,
71
+ set_name=getattr(
72
+ self.config,
73
+ "redis_port_key",
74
+ "boxlite:ports",
75
+ ),
76
+ )
77
+ self.ports_cache = RedisMapping(
78
+ redis_client,
79
+ prefix=getattr(self.config, "redis_port_key", "boxlite:ports"),
80
+ )
81
+ else:
82
+ # Use in-memory collections
83
+ self.port_set = InMemorySetCollection()
84
+ self.ports_cache = InMemoryMapping()
85
+
86
+ # Initialize BoxLite runtime
87
+ try:
88
+ from ...sandbox.constant import REGISTRY
89
+
90
+ if REGISTRY:
91
+ image_registries = [REGISTRY]
92
+ else:
93
+ image_registries = ["ghcr.io", "docker.io"]
94
+
95
+ options = boxlite.Options(
96
+ image_registries=image_registries,
97
+ )
98
+ self.runtime = SyncBoxlite(options=options)
99
+ self.runtime.start()
100
+ except Exception as e:
101
+ raise RuntimeError(
102
+ f"BoxLite client initialization failed: {str(e)}\n"
103
+ "Solutions:\n"
104
+ "• Ensure BoxLite is properly installed\n"
105
+ "• Check BoxLite runtime configuration",
106
+ ) from e
107
+
108
+ atexit.register(self._cleanup_runtime)
109
+
110
+ def _cleanup_runtime(self):
111
+ try:
112
+ if hasattr(self, "runtime") and self.runtime:
113
+ if hasattr(self.runtime, "__exit__"):
114
+ self.runtime.__exit__(None, None, None)
115
+ elif hasattr(self.runtime, "close"):
116
+ self.runtime.stop()
117
+ logger.info("BoxLite runtime cleaned up via atexit.")
118
+ except Exception as e:
119
+ logger.warning(f"An error occurred during BoxLite cleanup: {e}")
120
+ logger.debug(traceback.format_exc())
121
+
122
+ def create(
123
+ self,
124
+ image,
125
+ name=None,
126
+ ports=None,
127
+ volumes=None,
128
+ environment=None,
129
+ runtime_config=None,
130
+ ):
131
+ """
132
+ Create a new BoxLite box.
133
+
134
+ Args:
135
+ image: Container image to use
136
+ name: Optional name for the box
137
+ ports: List of container ports to expose (e.g., [8080, 3000])
138
+ volumes: List of volume mounts as
139
+ (host_path, guest_path, mode) tuples
140
+ environment: Dict of environment variables
141
+ runtime_config: Additional runtime configuration
142
+ (cpus, memory_mib, etc.)
143
+
144
+ Returns:
145
+ Tuple of (container_id, host_ports, host) or (None, None,
146
+ None) on failure
147
+ """
148
+ if runtime_config is None:
149
+ runtime_config = {}
150
+
151
+ port_mapping = {}
152
+
153
+ if ports:
154
+ free_ports = self._find_free_ports(len(ports))
155
+ for container_port, host_port in zip(ports, free_ports):
156
+ port_mapping[container_port] = host_port
157
+
158
+ try:
159
+ # Convert environment dict to list of tuples
160
+ env_list = []
161
+ if environment:
162
+ env_list = list(environment.items())
163
+
164
+ # Convert volumes to BoxLite format
165
+ volume_list = []
166
+ if volumes:
167
+ for vol in volumes:
168
+ if isinstance(vol, (list, tuple)) and len(vol) >= 2:
169
+ host_path = vol[0]
170
+ guest_path = vol[1]
171
+ read_only = len(vol) > 2 and vol[2] in (
172
+ "ro",
173
+ "readonly",
174
+ True,
175
+ )
176
+ volume_list.append(
177
+ (
178
+ host_path,
179
+ guest_path,
180
+ "ro" if read_only else "rw",
181
+ ),
182
+ )
183
+
184
+ # Convert ports to BoxLite format
185
+ port_list = []
186
+ for container_port, host_port in port_mapping.items():
187
+ if isinstance(container_port, str):
188
+ if "/" in container_port:
189
+ container_port = container_port.split("/")[0]
190
+ port_list.append((int(host_port), int(container_port), "tcp"))
191
+
192
+ # Create BoxOptions
193
+ box_options = boxlite.BoxOptions(
194
+ image=image,
195
+ env=env_list,
196
+ volumes=volume_list,
197
+ ports=port_list,
198
+ auto_remove=False, # We'll manage removal ourselves
199
+ detach=True,
200
+ **runtime_config,
201
+ )
202
+
203
+ # Create the box
204
+ box = self.runtime.create(box_options, name=name)
205
+ box_id = box.id
206
+
207
+ logger.debug(f"✓ Box created: {box.id}")
208
+
209
+ # Execute command (mirrors: await box.exec())
210
+ execution = box.exec("echo", ["Hello from default runtime"])
211
+ stdout = execution.stdout()
212
+
213
+ logger.debug("Output:")
214
+ for line in stdout: # Regular for loop, not async for
215
+ logger.debug(f" {line.strip()}")
216
+
217
+ exec_result = execution.wait() # No await
218
+ logger.debug(f"✓ Exit code: {exec_result.exit_code}")
219
+
220
+ # Store port mapping
221
+ if port_mapping:
222
+ self.ports_cache.set(box_id, list(port_mapping.values()))
223
+
224
+ return box_id, list(port_mapping.values()), "localhost"
225
+ except Exception as e:
226
+ logger.warning(f"An error occurred: {e}")
227
+ logger.debug(f"{traceback.format_exc()}")
228
+ return None, None, None
229
+
230
+ def start(self, container_id):
231
+ """
232
+ Start a BoxLite box.
233
+
234
+ Args:
235
+ container_id: Box ID or name
236
+
237
+ Returns:
238
+ bool: True if successful, False otherwise
239
+ """
240
+ try:
241
+ box = self.runtime.get(container_id)
242
+ if box is None:
243
+ logger.warning(f"Box '{container_id}' not found")
244
+ return False
245
+
246
+ box.start()
247
+ return True
248
+ except Exception as e:
249
+ logger.warning(f"An error occurred: {e}")
250
+ logger.debug(f"{traceback.format_exc()}")
251
+ return False
252
+
253
+ def stop(self, container_id, timeout=None):
254
+ """
255
+ Stop a BoxLite box.
256
+
257
+ Args:
258
+ container_id: Box ID or name
259
+ timeout: Optional timeout in seconds (not used in BoxLite)
260
+
261
+ Returns:
262
+ bool: True if successful, False otherwise
263
+ """
264
+ try:
265
+ box = self.runtime.get(container_id)
266
+ if box is None:
267
+ logger.warning(f"Box '{container_id}' not found")
268
+ return False
269
+
270
+ # Stop the box
271
+ box.stop()
272
+ return True
273
+ except Exception as e:
274
+ logger.warning(f"An error occurred: {e}")
275
+ logger.debug(f"{traceback.format_exc()}")
276
+ return False
277
+
278
+ def remove(self, container_id, force=False):
279
+ """
280
+ Remove a BoxLite box.
281
+
282
+ Args:
283
+ container_id: Box ID or name
284
+ force: If True, stop the box first if running
285
+
286
+ Returns:
287
+ bool: True if successful, False otherwise
288
+ """
289
+ try:
290
+ # Get ports before removal
291
+ ports = self.ports_cache.get(container_id)
292
+
293
+ # Remove the box
294
+ self.runtime.remove(container_id, force=force)
295
+
296
+ # Clean up port cache
297
+ self.ports_cache.delete(container_id)
298
+
299
+ # Remove ports from port set
300
+ if ports:
301
+ for host_port in ports:
302
+ self.port_set.remove(host_port)
303
+
304
+ return True
305
+ except Exception as e:
306
+ logger.warning(f"An error occurred: {e}")
307
+ logger.debug(f"{traceback.format_exc()}")
308
+ return False
309
+
310
+ def inspect(self, container_id):
311
+ """
312
+ Inspect a BoxLite box.
313
+
314
+ Args:
315
+ container_id: Box ID or name
316
+
317
+ Returns:
318
+ Dict with box information or None if not found
319
+ """
320
+ try:
321
+ box = self.runtime.get(container_id)
322
+
323
+ if box is None:
324
+ return None
325
+
326
+ info = box.info()
327
+ ports = self.ports_cache.get(container_id) or []
328
+
329
+ # Convert BoxInfo to dict format similar to Docker
330
+ return {
331
+ "Id": info.id,
332
+ "Name": info.name or "",
333
+ "State": {
334
+ "Status": info.state.status,
335
+ "Running": info.state.running,
336
+ "Paused": False,
337
+ "Restarting": False,
338
+ "OOMKilled": False,
339
+ "Dead": not info.state.running,
340
+ "Pid": info.state.pid or 0,
341
+ "ExitCode": 0 if info.state.running else 1,
342
+ "Error": "",
343
+ "StartedAt": info.created_at,
344
+ "FinishedAt": ""
345
+ if info.state.running
346
+ else info.created_at,
347
+ },
348
+ "Created": info.created_at,
349
+ "Image": info.image,
350
+ "Config": {
351
+ "Env": [], # BoxInfo doesn't expose env directly
352
+ },
353
+ "NetworkSettings": {
354
+ "Ports": self._format_ports(ports),
355
+ },
356
+ "HostConfig": {
357
+ "CpuCount": info.cpus,
358
+ "Memory": info.memory_mib * 1024 * 1024,
359
+ # Convert MiB to bytes
360
+ },
361
+ }
362
+ except Exception as e:
363
+ logger.warning(f"An error occurred: {e}")
364
+ logger.debug(f"{traceback.format_exc()}")
365
+ return None
366
+
367
+ def get_status(self, container_id):
368
+ """
369
+ Get the current status of the specified box.
370
+
371
+ Args:
372
+ container_id: Box ID or name
373
+
374
+ Returns:
375
+ str: Status string ('running', 'stopped', etc.) or None if not
376
+ found
377
+ """
378
+ box_attrs = self.inspect(container_id=container_id)
379
+ if box_attrs:
380
+ return box_attrs["State"]["Status"]
381
+ return None
382
+
383
+ def _find_free_ports(self, n):
384
+ """
385
+ Find n free ports in the configured port range.
386
+
387
+ Args:
388
+ n: Number of ports to find
389
+
390
+ Returns:
391
+ List of free port numbers
392
+
393
+ Raises:
394
+ RuntimeError: If not enough free ports are available
395
+ """
396
+ free_ports = []
397
+
398
+ for port in self.port_range:
399
+ if len(free_ports) >= n:
400
+ break # We have found enough ports
401
+
402
+ if not self.port_set.add(port):
403
+ continue # Port already in set
404
+
405
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
406
+ try:
407
+ s.bind(("", port))
408
+ free_ports.append(port) # Port is available
409
+ except OSError:
410
+ # Bind failed, port is in use
411
+ self.port_set.remove(port)
412
+ # Try the next one
413
+ continue
414
+
415
+ if len(free_ports) < n:
416
+ raise RuntimeError(
417
+ "Not enough free ports available in the specified range.",
418
+ )
419
+
420
+ return free_ports
421
+
422
+ def _format_ports(self, host_ports):
423
+ """
424
+ Format port list for Docker-like inspect output.
425
+
426
+ Args:
427
+ host_ports: List of host port numbers
428
+
429
+ Returns:
430
+ Dict formatted like Docker's NetworkSettings.Ports
431
+ """
432
+ if not host_ports:
433
+ return {}
434
+
435
+ ports = {}
436
+ for host_port in host_ports:
437
+ # We don't have the container port info here, so we'll use the
438
+ # host port as both host and container port
439
+ key = f"{host_port}/tcp"
440
+ ports[key] = [{"HostIp": "0.0.0.0", "HostPort": str(host_port)}]
441
+
442
+ return ports
@@ -17,26 +17,6 @@ from ..collections import (
17
17
  logger = logging.getLogger(__name__)
18
18
 
19
19
 
20
- def is_port_available(port):
21
- """
22
- Check if a given port is available (not in use) on the local system.
23
-
24
- Args:
25
- port (int): The port number to check.
26
-
27
- Returns:
28
- bool: True if the port is available, False if it is in use.
29
- """
30
- with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
31
- try:
32
- s.bind(("", port))
33
- # Port is available
34
- return True
35
- except OSError:
36
- # Port is in use
37
- return False
38
-
39
-
40
20
  class DockerClient(BaseClient):
41
21
  def __init__(self, config=None):
42
22
  self.config = config
@@ -827,13 +827,15 @@ class FCClient(BaseClient):
827
827
  """
828
828
  replacement_map = {
829
829
  "agentscope/runtime-sandbox-base": "serverless-registry.cn-hangzhou.cr.aliyuncs.com/functionai" # noqa: E501
830
- "/agentscope_runtime-sandbox-base:20251027",
830
+ "/agentscope_runtime-sandbox-base:20260106",
831
831
  "agentscope/runtime-sandbox-browser": "serverless-registry.cn-hangzhou.cr.aliyuncs.com/functionai" # noqa: E501
832
- "/agentscope_runtime-sandbox-browser:20251027",
832
+ "/agentscope_runtime-sandbox-browser:20260106",
833
833
  "agentscope/runtime-sandbox-filesystem": "serverless-registry.cn-hangzhou.cr.aliyuncs.com/functionai" # noqa: E501
834
- "/agentscope_runtime-sandbox-filesystem:20251027",
834
+ "/agentscope_runtime-sandbox-filesystem:20260106",
835
835
  "agentscope/runtime-sandbox-gui": "serverless-registry.cn-hangzhou.cr.aliyuncs.com/functionai" # noqa: E501
836
- "/agentscope_runtime-sandbox-gui:20251027",
836
+ "/agentscope_runtime-sandbox-gui:20260106",
837
+ "agentscope/runtime-sandbox-mobile": "serverless-registry.cn-hangzhou.cr.aliyuncs.com/functionai" # noqa: E501
838
+ "/agentscope_runtime-sandbox-mobile:20251217",
837
839
  }
838
840
 
839
841
  if ":" in image:
@@ -0,0 +1,38 @@
1
+ # -*- coding: utf-8 -*-
2
+ import logging
3
+ from .docker_client import DockerClient
4
+
5
+ logger = logging.getLogger(__name__)
6
+
7
+
8
+ class GVisorDockerClient(DockerClient):
9
+ """
10
+ A DockerClient that enforces gVisor runtime (`runsc`).
11
+ """
12
+
13
+ def create(
14
+ self,
15
+ image,
16
+ name=None,
17
+ ports=None,
18
+ volumes=None,
19
+ environment=None,
20
+ runtime_config=None,
21
+ ):
22
+ if runtime_config is None:
23
+ runtime_config = {}
24
+
25
+ runtime_config["runtime"] = "runsc"
26
+
27
+ logger.debug(
28
+ f"[GVisorDockerClient] Forcing runtime=runsc for image {image}",
29
+ )
30
+
31
+ return super().create(
32
+ image=image,
33
+ name=name,
34
+ ports=ports,
35
+ volumes=volumes,
36
+ environment=environment,
37
+ runtime_config=runtime_config,
38
+ )