agentscope-runtime 0.1.5b2__py3-none-any.whl → 0.1.6__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 (90) hide show
  1. agentscope_runtime/engine/agents/agentscope_agent.py +447 -0
  2. agentscope_runtime/engine/agents/agno_agent.py +19 -18
  3. agentscope_runtime/engine/agents/autogen_agent.py +13 -8
  4. agentscope_runtime/engine/agents/utils.py +53 -0
  5. agentscope_runtime/engine/deployers/__init__.py +0 -13
  6. agentscope_runtime/engine/deployers/local_deployer.py +501 -356
  7. agentscope_runtime/engine/helpers/helper.py +60 -41
  8. agentscope_runtime/engine/runner.py +11 -36
  9. agentscope_runtime/engine/schemas/agent_schemas.py +2 -70
  10. agentscope_runtime/engine/services/sandbox_service.py +62 -70
  11. agentscope_runtime/engine/services/tablestore_memory_service.py +304 -0
  12. agentscope_runtime/engine/services/tablestore_rag_service.py +143 -0
  13. agentscope_runtime/engine/services/tablestore_session_history_service.py +293 -0
  14. agentscope_runtime/engine/services/utils/tablestore_service_utils.py +352 -0
  15. agentscope_runtime/sandbox/__init__.py +2 -0
  16. agentscope_runtime/sandbox/box/base/__init__.py +4 -0
  17. agentscope_runtime/sandbox/box/base/base_sandbox.py +4 -3
  18. agentscope_runtime/sandbox/box/browser/__init__.py +4 -0
  19. agentscope_runtime/sandbox/box/browser/browser_sandbox.py +8 -13
  20. agentscope_runtime/sandbox/box/dummy/__init__.py +4 -0
  21. agentscope_runtime/sandbox/box/filesystem/__init__.py +4 -0
  22. agentscope_runtime/sandbox/box/filesystem/filesystem_sandbox.py +8 -6
  23. agentscope_runtime/sandbox/box/gui/__init__.py +4 -0
  24. agentscope_runtime/sandbox/box/gui/gui_sandbox.py +80 -0
  25. agentscope_runtime/sandbox/box/sandbox.py +5 -2
  26. agentscope_runtime/sandbox/box/shared/routers/generic.py +20 -1
  27. agentscope_runtime/sandbox/box/training_box/__init__.py +4 -0
  28. agentscope_runtime/sandbox/box/training_box/training_box.py +10 -15
  29. agentscope_runtime/sandbox/build.py +143 -58
  30. agentscope_runtime/sandbox/client/http_client.py +43 -49
  31. agentscope_runtime/sandbox/client/training_client.py +0 -1
  32. agentscope_runtime/sandbox/constant.py +24 -1
  33. agentscope_runtime/sandbox/custom/custom_sandbox.py +5 -5
  34. agentscope_runtime/sandbox/custom/example.py +2 -2
  35. agentscope_runtime/sandbox/enums.py +1 -0
  36. agentscope_runtime/sandbox/manager/collections/in_memory_mapping.py +11 -6
  37. agentscope_runtime/sandbox/manager/collections/redis_mapping.py +25 -9
  38. agentscope_runtime/sandbox/manager/container_clients/__init__.py +0 -10
  39. agentscope_runtime/sandbox/manager/container_clients/agentrun_client.py +1098 -0
  40. agentscope_runtime/sandbox/manager/container_clients/docker_client.py +33 -205
  41. agentscope_runtime/sandbox/manager/container_clients/kubernetes_client.py +8 -555
  42. agentscope_runtime/sandbox/manager/sandbox_manager.py +187 -88
  43. agentscope_runtime/sandbox/manager/server/app.py +82 -14
  44. agentscope_runtime/sandbox/manager/server/config.py +50 -3
  45. agentscope_runtime/sandbox/model/container.py +6 -23
  46. agentscope_runtime/sandbox/model/manager_config.py +93 -5
  47. agentscope_runtime/sandbox/tools/gui/__init__.py +7 -0
  48. agentscope_runtime/sandbox/tools/gui/tool.py +77 -0
  49. agentscope_runtime/sandbox/tools/mcp_tool.py +6 -2
  50. agentscope_runtime/sandbox/utils.py +124 -0
  51. agentscope_runtime/version.py +1 -1
  52. {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.1.6.dist-info}/METADATA +168 -77
  53. {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.1.6.dist-info}/RECORD +59 -78
  54. {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.1.6.dist-info}/entry_points.txt +0 -1
  55. agentscope_runtime/engine/agents/agentscope_agent/__init__.py +0 -6
  56. agentscope_runtime/engine/agents/agentscope_agent/agent.py +0 -401
  57. agentscope_runtime/engine/agents/agentscope_agent/hooks.py +0 -169
  58. agentscope_runtime/engine/agents/llm_agent.py +0 -51
  59. agentscope_runtime/engine/deployers/adapter/responses/response_api_adapter_utils.py +0 -2886
  60. agentscope_runtime/engine/deployers/adapter/responses/response_api_agent_adapter.py +0 -51
  61. agentscope_runtime/engine/deployers/adapter/responses/response_api_protocol_adapter.py +0 -314
  62. agentscope_runtime/engine/deployers/cli_fc_deploy.py +0 -184
  63. agentscope_runtime/engine/deployers/kubernetes_deployer.py +0 -265
  64. agentscope_runtime/engine/deployers/modelstudio_deployer.py +0 -677
  65. agentscope_runtime/engine/deployers/utils/deployment_modes.py +0 -14
  66. agentscope_runtime/engine/deployers/utils/docker_image_utils/__init__.py +0 -8
  67. agentscope_runtime/engine/deployers/utils/docker_image_utils/docker_image_builder.py +0 -429
  68. agentscope_runtime/engine/deployers/utils/docker_image_utils/dockerfile_generator.py +0 -240
  69. agentscope_runtime/engine/deployers/utils/docker_image_utils/runner_image_factory.py +0 -297
  70. agentscope_runtime/engine/deployers/utils/package_project_utils.py +0 -932
  71. agentscope_runtime/engine/deployers/utils/service_utils/__init__.py +0 -9
  72. agentscope_runtime/engine/deployers/utils/service_utils/fastapi_factory.py +0 -504
  73. agentscope_runtime/engine/deployers/utils/service_utils/fastapi_templates.py +0 -157
  74. agentscope_runtime/engine/deployers/utils/service_utils/process_manager.py +0 -268
  75. agentscope_runtime/engine/deployers/utils/service_utils/service_config.py +0 -75
  76. agentscope_runtime/engine/deployers/utils/service_utils/service_factory.py +0 -220
  77. agentscope_runtime/engine/deployers/utils/wheel_packager.py +0 -389
  78. agentscope_runtime/engine/helpers/agent_api_builder.py +0 -651
  79. agentscope_runtime/engine/llms/__init__.py +0 -3
  80. agentscope_runtime/engine/llms/base_llm.py +0 -60
  81. agentscope_runtime/engine/llms/qwen_llm.py +0 -47
  82. agentscope_runtime/engine/schemas/embedding.py +0 -37
  83. agentscope_runtime/engine/schemas/modelstudio_llm.py +0 -310
  84. agentscope_runtime/engine/schemas/oai_llm.py +0 -538
  85. agentscope_runtime/engine/schemas/realtime.py +0 -254
  86. /agentscope_runtime/engine/{deployers/adapter/responses → services/utils}/__init__.py +0 -0
  87. /agentscope_runtime/{engine/deployers/utils → sandbox/box/gui/box}/__init__.py +0 -0
  88. {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.1.6.dist-info}/WHEEL +0 -0
  89. {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.1.6.dist-info}/licenses/LICENSE +0 -0
  90. {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.1.6.dist-info}/top_level.txt +0 -0
@@ -3,10 +3,11 @@
3
3
  from typing import Optional
4
4
  from urllib.parse import urlparse, urlunparse
5
5
 
6
- from ...constant import IMAGE_TAG
6
+ from ...utils import build_image_uri
7
7
  from ...registry import SandboxRegistry
8
8
  from ...enums import SandboxType
9
- from ...box.sandbox import Sandbox
9
+ from ...box.base import BaseSandbox
10
+ from ...box.gui import GUIMixin
10
11
 
11
12
 
12
13
  def http_to_ws(url, use_localhost=True):
@@ -27,35 +28,29 @@ def http_to_ws(url, use_localhost=True):
27
28
 
28
29
 
29
30
  @SandboxRegistry.register(
30
- f"agentscope/runtime-sandbox-browser:{IMAGE_TAG}",
31
+ build_image_uri("runtime-sandbox-browser"),
31
32
  sandbox_type=SandboxType.BROWSER,
32
33
  security_level="medium",
33
34
  timeout=60,
34
35
  description="Browser sandbox",
35
36
  )
36
- class BrowserSandbox(Sandbox):
37
- def __init__(
37
+ class BrowserSandbox(GUIMixin, BaseSandbox):
38
+ def __init__( # pylint: disable=useless-parent-delegation
38
39
  self,
39
40
  sandbox_id: Optional[str] = None,
40
41
  timeout: int = 3000,
41
42
  base_url: Optional[str] = None,
42
43
  bearer_token: Optional[str] = None,
44
+ sandbox_type: SandboxType = SandboxType.BROWSER,
43
45
  ):
44
46
  super().__init__(
45
47
  sandbox_id,
46
48
  timeout,
47
49
  base_url,
48
50
  bearer_token,
49
- SandboxType.BROWSER,
51
+ sandbox_type,
50
52
  )
51
53
 
52
- @property
53
- def browser_ws(self):
54
- if self.base_url is None:
55
- # Local mode
56
- return self.get_info()["front_browser_ws"]
57
- return http_to_ws(f"{self.base_url}/browser/{self.sandbox_id}/cast")
58
-
59
54
  def browser_close(self):
60
55
  return self.call_tool("browser_close", {})
61
56
 
@@ -0,0 +1,4 @@
1
+ # -*- coding: utf-8 -*-
2
+ from .dummy_sandbox import DummySandbox
3
+
4
+ __all__ = ["DummySandbox"]
@@ -0,0 +1,4 @@
1
+ # -*- coding: utf-8 -*-
2
+ from .filesystem_sandbox import FilesystemSandbox
3
+
4
+ __all__ = ["FilesystemSandbox"]
@@ -2,33 +2,35 @@
2
2
  # pylint: disable=dangerous-default-value
3
3
  from typing import Optional
4
4
 
5
- from ...constant import IMAGE_TAG
5
+ from ...utils import build_image_uri
6
6
  from ...registry import SandboxRegistry
7
7
  from ...enums import SandboxType
8
- from ...box.sandbox import Sandbox
8
+ from ...box.base import BaseSandbox
9
+ from ...box.gui import GUIMixin
9
10
 
10
11
 
11
12
  @SandboxRegistry.register(
12
- f"agentscope/runtime-sandbox-filesystem:{IMAGE_TAG}",
13
+ build_image_uri("runtime-sandbox-filesystem"),
13
14
  sandbox_type=SandboxType.FILESYSTEM,
14
15
  security_level="medium",
15
16
  timeout=60,
16
17
  description="Filesystem sandbox",
17
18
  )
18
- class FilesystemSandbox(Sandbox):
19
- def __init__(
19
+ class FilesystemSandbox(GUIMixin, BaseSandbox):
20
+ def __init__( # pylint: disable=useless-parent-delegation
20
21
  self,
21
22
  sandbox_id: Optional[str] = None,
22
23
  timeout: int = 3000,
23
24
  base_url: Optional[str] = None,
24
25
  bearer_token: Optional[str] = None,
26
+ sandbox_type: SandboxType = SandboxType.FILESYSTEM,
25
27
  ):
26
28
  super().__init__(
27
29
  sandbox_id,
28
30
  timeout,
29
31
  base_url,
30
32
  bearer_token,
31
- SandboxType.FILESYSTEM,
33
+ sandbox_type,
32
34
  )
33
35
 
34
36
  def read_file(self, path: str):
@@ -0,0 +1,4 @@
1
+ # -*- coding: utf-8 -*-
2
+ from .gui_sandbox import GuiSandbox, GUIMixin
3
+
4
+ __all__ = ["GuiSandbox", "GUIMixin"]
@@ -0,0 +1,80 @@
1
+ # -*- coding: utf-8 -*-
2
+ import logging
3
+ from typing import Optional, Union, Tuple, List
4
+
5
+ from urllib.parse import urljoin, urlencode
6
+
7
+ from ...utils import build_image_uri, get_platform
8
+ from ...registry import SandboxRegistry
9
+ from ...enums import SandboxType
10
+ from ...box.base import BaseSandbox
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class GUIMixin:
16
+ @property
17
+ def desktop_url(self):
18
+ if not self.manager_api.check_health(identity=self.sandbox_id):
19
+ raise RuntimeError(f"Sandbox {self.sandbox_id} is not healthy")
20
+
21
+ info = self.get_info()
22
+ path = "/vnc/vnc_lite.html"
23
+ remote_path = "/vnc/vnc_relay.html"
24
+ params = {"password": info["runtime_token"]}
25
+
26
+ if self.base_url is None:
27
+ return urljoin(info["url"], path) + "?" + urlencode(params)
28
+
29
+ return (
30
+ f"{self.base_url}/desktop/{self.sandbox_id}{remote_path}"
31
+ f"?{urlencode(params)}"
32
+ )
33
+
34
+
35
+ @SandboxRegistry.register(
36
+ build_image_uri("runtime-sandbox-gui"),
37
+ sandbox_type=SandboxType.GUI,
38
+ security_level="high",
39
+ timeout=30,
40
+ description="GUI Sandbox",
41
+ )
42
+ class GuiSandbox(GUIMixin, BaseSandbox):
43
+ def __init__( # pylint: disable=useless-parent-delegation
44
+ self,
45
+ sandbox_id: Optional[str] = None,
46
+ timeout: int = 3000,
47
+ base_url: Optional[str] = None,
48
+ bearer_token: Optional[str] = None,
49
+ sandbox_type: SandboxType = SandboxType.GUI,
50
+ ):
51
+ super().__init__(
52
+ sandbox_id,
53
+ timeout,
54
+ base_url,
55
+ bearer_token,
56
+ sandbox_type,
57
+ )
58
+ if get_platform() == "linux/arm64":
59
+ logger.warning(
60
+ "\nCompatibility Notice: This GUI Sandbox may have issues on "
61
+ "arm64 CPU architectures, due to the computer-use-mcp does "
62
+ "not provide linux/arm64 compatibility. It has been tested "
63
+ "to work on Apple M4 chips with Rosetta enabled. However, "
64
+ "on M1, M2, and M3 chips, chromium browser might crash due "
65
+ "to the missing SSE3 instruction set.",
66
+ )
67
+
68
+ def computer_use(
69
+ self,
70
+ action: str,
71
+ coordinate: Optional[Union[List[float], Tuple[float, float]]] = None,
72
+ text: Optional[str] = None,
73
+ ):
74
+ payload = {"action": action}
75
+ if coordinate is not None:
76
+ payload["coordinate"] = coordinate
77
+ if text is not None:
78
+ payload["text"] = text
79
+
80
+ return self.call_tool("computer", payload)
@@ -49,7 +49,7 @@ class Sandbox:
49
49
  )
50
50
 
51
51
  sandbox_id = self.manager_api.create_from_pool(
52
- sandbox_type=sandbox_type.value,
52
+ sandbox_type=SandboxType(sandbox_type).value,
53
53
  )
54
54
  if sandbox_id is None:
55
55
  raise RuntimeError(
@@ -115,8 +115,11 @@ class Sandbox:
115
115
  # Clean the specific sandbox
116
116
  self.manager_api.release(self.sandbox_id)
117
117
  except Exception as e:
118
+ import traceback
119
+
118
120
  logger.error(
119
- f"Cleanup {self.sandbox_id} error: {e}",
121
+ f"Cleanup {self.sandbox_id} error: {e}\n"
122
+ f"{traceback.format_exc()}",
120
123
  )
121
124
 
122
125
  def __enter__(self):
@@ -1,5 +1,6 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  import io
3
+ import sys
3
4
  import logging
4
5
  import subprocess
5
6
  import traceback
@@ -44,7 +45,25 @@ async def run_ipython_cell(
44
45
  stderr_buf = io.StringIO()
45
46
 
46
47
  with redirect_stdout(stdout_buf), redirect_stderr(stderr_buf):
47
- ipy.run_cell(code)
48
+ preprocessing_exc_tuple = None
49
+ try:
50
+ transformed_cell = ipy.transform_cell(code)
51
+ except Exception:
52
+ transformed_cell = code
53
+ preprocessing_exc_tuple = sys.exc_info()
54
+
55
+ if transformed_cell is None:
56
+ raise HTTPException(
57
+ status_code=500,
58
+ detail="IPython cell transformation failed: "
59
+ "transformed_cell is None.",
60
+ )
61
+
62
+ await ipy.run_cell_async(
63
+ code,
64
+ transformed_cell=transformed_cell,
65
+ preprocessing_exc_tuple=preprocessing_exc_tuple,
66
+ )
48
67
 
49
68
  stdout_content = stdout_buf.getvalue()
50
69
  stderr_content = stderr_buf.getvalue()
@@ -0,0 +1,4 @@
1
+ # -*- coding: utf-8 -*-
2
+ from .training_box import APPWorldSandbox, BFCLSandbox
3
+
4
+ __all__ = ["APPWorldSandbox", "BFCLSandbox"]
@@ -5,21 +5,13 @@ Module for the Training Sandbox implementation.
5
5
  This module provides a sandbox environment for training tasks
6
6
  with specific configuration and tool calling methods.
7
7
  """
8
- import platform
9
8
  from typing import Dict, Optional
10
9
  import os
11
10
 
11
+ from ...utils import build_image_uri
12
12
  from ...registry import SandboxRegistry
13
13
  from ...enums import SandboxType
14
14
  from ...box.sandbox import Sandbox
15
- from ...constant import IMAGE_TAG
16
-
17
-
18
- def get_image_tag() -> str:
19
- machine = platform.machine().lower()
20
- if machine in ("arm64", "aarch64", "armv7l", "armv8"):
21
- return f"{IMAGE_TAG}-arm64"
22
- return IMAGE_TAG
23
15
 
24
16
 
25
17
  class TrainingSandbox(Sandbox):
@@ -214,7 +206,7 @@ class TrainingSandbox(Sandbox):
214
206
 
215
207
 
216
208
  @SandboxRegistry.register(
217
- f"agentscope/runtime-sandbox-appworld:{get_image_tag()}",
209
+ build_image_uri("runtime-sandbox-appworld", arm64_compatible=False),
218
210
  sandbox_type=SandboxType.APPWORLD,
219
211
  runtime_config={"shm_size": "5.06gb"},
220
212
  security_level="medium",
@@ -235,6 +227,7 @@ class APPWorldSandbox(TrainingSandbox):
235
227
  timeout: int = 3000,
236
228
  base_url: Optional[str] = None,
237
229
  bearer_token: Optional[str] = None,
230
+ sandbox_type: SandboxType = SandboxType.APPWORLD,
238
231
  ):
239
232
  """
240
233
  Initialize the Training Sandbox.
@@ -250,7 +243,7 @@ class APPWorldSandbox(TrainingSandbox):
250
243
  timeout,
251
244
  base_url,
252
245
  bearer_token,
253
- SandboxType.APPWORLD,
246
+ sandbox_type,
254
247
  )
255
248
 
256
249
 
@@ -258,7 +251,7 @@ DATASET_SUB_TYPE = os.environ.get("DATASET_SUB_TYPE", "multi_turn")
258
251
 
259
252
 
260
253
  @SandboxRegistry.register(
261
- f"agentscope/runtime-sandbox-bfcl:{get_image_tag()}",
254
+ build_image_uri("runtime-sandbox-bfcl", arm64_compatible=False),
262
255
  sandbox_type=SandboxType.BFCL,
263
256
  runtime_config={"shm_size": "8.06gb"},
264
257
  security_level="medium",
@@ -289,6 +282,7 @@ class BFCLSandbox(TrainingSandbox):
289
282
  timeout: int = 3000,
290
283
  base_url: Optional[str] = None,
291
284
  bearer_token: Optional[str] = None,
285
+ sandbox_type: SandboxType = SandboxType.BFCL,
292
286
  ):
293
287
  """
294
288
  Initialize the Training Sandbox.
@@ -304,12 +298,12 @@ class BFCLSandbox(TrainingSandbox):
304
298
  timeout,
305
299
  base_url,
306
300
  bearer_token,
307
- SandboxType.BFCL,
301
+ sandbox_type,
308
302
  )
309
303
 
310
304
 
311
305
  @SandboxRegistry.register(
312
- f"agentscope/runtime-sandbox-webshop:{get_image_tag()}",
306
+ build_image_uri("runtime-sandbox-webshop", arm64_compatible=False),
313
307
  sandbox_type=SandboxType.WEBSHOP,
314
308
  runtime_config={"shm_size": "5.06gb"},
315
309
  security_level="medium",
@@ -330,6 +324,7 @@ class WebShopSandbox(TrainingSandbox):
330
324
  timeout: int = 3000,
331
325
  base_url: Optional[str] = None,
332
326
  bearer_token: Optional[str] = None,
327
+ sandbox_type: SandboxType = SandboxType.WEBSHOP,
333
328
  ):
334
329
  """
335
330
  Initialize the Training Sandbox.
@@ -345,5 +340,5 @@ class WebShopSandbox(TrainingSandbox):
345
340
  timeout,
346
341
  base_url,
347
342
  bearer_token,
348
- SandboxType.BFCL,
343
+ sandbox_type,
349
344
  )
@@ -1,5 +1,5 @@
1
1
  # -*- coding: utf-8 -*-
2
- # pylint: disable=too-many-statements
2
+ # pylint: disable=too-many-statements,too-many-branches
3
3
  import argparse
4
4
  import logging
5
5
  import os
@@ -11,6 +11,15 @@ import requests
11
11
 
12
12
  from .enums import SandboxType
13
13
  from .registry import SandboxRegistry
14
+ from .utils import dynamic_import, get_platform
15
+
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+ DOCKER_PLATFORMS = [
20
+ "linux/amd64",
21
+ "linux/arm64",
22
+ ]
14
23
 
15
24
 
16
25
  def find_free_port(start_port, end_port):
@@ -18,7 +27,7 @@ def find_free_port(start_port, end_port):
18
27
  with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
19
28
  if sock.connect_ex(("localhost", port)) != 0:
20
29
  return port
21
- logging.error(
30
+ logger.error(
22
31
  f"No free ports available in the range {start_port}-{end_port}",
23
32
  )
24
33
  raise RuntimeError(
@@ -30,7 +39,7 @@ def check_health(url, secret_token, timeout=120, interval=5):
30
39
  headers = {"Authorization": f"Bearer {secret_token}"}
31
40
  spent_time = 0
32
41
  while spent_time < timeout:
33
- logging.info(
42
+ logger.info(
34
43
  f"Attempting to connect to {url} (Elapsed time: {spent_time} "
35
44
  f"seconds)...",
36
45
  )
@@ -41,35 +50,52 @@ def check_health(url, secret_token, timeout=120, interval=5):
41
50
  return True
42
51
  except requests.exceptions.RequestException:
43
52
  pass
44
- logging.info(
53
+ logger.info(
45
54
  f"Health check failed for {url}. Retrying in {interval} "
46
55
  f"seconds...",
47
56
  )
48
57
  time.sleep(interval)
49
58
  spent_time += interval
50
- logging.error(f"Health check failed for {url} after {timeout} seconds.")
59
+ logger.error(f"Health check failed for {url} after {timeout} seconds.")
51
60
  return False
52
61
 
53
62
 
54
- def build_image(build_type, dockerfile_path=None):
63
+ def build_image(
64
+ build_type,
65
+ dockerfile_path=None,
66
+ platform_choice="linux/amd64",
67
+ ):
68
+ assert platform_choice in DOCKER_PLATFORMS, (
69
+ f"Invalid platform: {platform_choice}. Valid options:"
70
+ f" {DOCKER_PLATFORMS}"
71
+ )
72
+
73
+ platform_tag = ""
74
+ if platform_choice == "linux/arm64":
75
+ platform_tag = "-arm64"
76
+
77
+ buildx_enable = platform_choice != get_platform()
78
+
55
79
  if dockerfile_path is None:
56
80
  dockerfile_path = (
57
81
  f"src/agentscope_runtime/sandbox/box/{build_type}/Dockerfile"
58
82
  )
59
83
 
60
- logging.info(f"Building {build_type} with `{dockerfile_path}`...")
84
+ logger.info(f"Building {build_type} with `{dockerfile_path}`...")
61
85
 
62
86
  # Initialize and update Git submodule
63
- logging.info("Initializing and updating Git submodule...")
87
+ logger.info("Initializing and updating Git submodule...")
64
88
  subprocess.run(
65
89
  ["git", "submodule", "update", "--init", "--recursive"],
66
90
  check=True,
67
91
  )
68
92
 
69
93
  secret_token = "secret_token123"
70
- image_name = SandboxRegistry.get_image_by_type(build_type)
71
94
 
72
- logging.info(f"Building Docker image {image_name}...")
95
+ # Add platform tag
96
+ image_name = SandboxRegistry.get_image_by_type(build_type) + platform_tag
97
+
98
+ logger.info(f"Building Docker image {image_name}...")
73
99
 
74
100
  # Check if image exists
75
101
  result = subprocess.run(
@@ -87,7 +113,7 @@ def build_image(build_type, dockerfile_path=None):
87
113
  f"you want to overwrite it? (y/N): ",
88
114
  )
89
115
  if choice.lower() != "y":
90
- logging.info("Exiting without overwriting the existing image.")
116
+ logger.info("Exiting without overwriting the existing image.")
91
117
  return
92
118
 
93
119
  if not os.path.exists(dockerfile_path):
@@ -97,70 +123,101 @@ def build_image(build_type, dockerfile_path=None):
97
123
  )
98
124
 
99
125
  # Build Docker image
100
- subprocess.run(
101
- [
102
- "docker",
103
- "build",
104
- "-f",
105
- dockerfile_path,
106
- "-t",
107
- f"{image_name}dev",
108
- ".",
109
- ],
110
- check=False,
111
- )
112
- logging.info(f"Docker image {image_name}dev built successfully.")
126
+ if not buildx_enable:
127
+ subprocess.run(
128
+ [
129
+ "docker",
130
+ "build",
131
+ "-f",
132
+ dockerfile_path,
133
+ "-t",
134
+ f"{image_name}dev",
135
+ ".",
136
+ ],
137
+ check=False,
138
+ )
139
+ else:
140
+ subprocess.run(
141
+ [
142
+ "docker",
143
+ "buildx",
144
+ "build",
145
+ "--platform",
146
+ platform_choice,
147
+ "-f",
148
+ dockerfile_path,
149
+ "-t",
150
+ f"{image_name}dev",
151
+ "--load",
152
+ ".",
153
+ ],
154
+ check=False,
155
+ )
113
156
 
114
- logging.info(f"Start to build image {image_name}.")
157
+ logger.info(f"Docker image {image_name}dev built successfully.")
158
+
159
+ logger.info(f"Start to build image {image_name}.")
115
160
 
116
161
  # Run the container with port mapping and environment variable
117
162
  free_port = find_free_port(8080, 8090)
118
- result = subprocess.run(
119
- [
120
- "docker",
121
- "run",
122
- "-d",
123
- "-p",
124
- f"{free_port}:80",
125
- "-e",
126
- f"SECRET_TOKEN={secret_token}",
127
- f"{image_name}dev",
128
- ],
129
- capture_output=True,
130
- text=True,
131
- check=False,
132
- )
163
+
164
+ if not buildx_enable:
165
+ result = subprocess.run(
166
+ [
167
+ "docker",
168
+ "run",
169
+ "-d",
170
+ "-p",
171
+ f"{free_port}:80",
172
+ "-e",
173
+ f"SECRET_TOKEN={secret_token}",
174
+ f"{image_name}dev",
175
+ ],
176
+ capture_output=True,
177
+ text=True,
178
+ check=False,
179
+ )
180
+ else:
181
+ result = subprocess.run(
182
+ [
183
+ "docker",
184
+ "run",
185
+ "--platform",
186
+ platform_choice,
187
+ "-d",
188
+ "-p",
189
+ f"{free_port}:80",
190
+ "-e",
191
+ f"SECRET_TOKEN={secret_token}",
192
+ f"{image_name}dev",
193
+ ],
194
+ capture_output=True,
195
+ text=True,
196
+ check=False,
197
+ )
198
+
133
199
  container_id = result.stdout.strip()
134
- logging.info(f"Running container {container_id} on port {free_port}")
200
+ logger.info(f"Running container {container_id} on port {free_port}")
135
201
 
136
202
  # Check health endpoints
137
203
  fastapi_health_url = f"http://localhost:{free_port}/fastapi/healthz"
138
- steelapi_health_url = (
139
- f"http://localhost:{free_port}/steel-api/{secret_token}/v1/health"
140
- )
141
204
 
142
205
  # Check health for FASTAPI
143
206
  fastapi_healthy = check_health(fastapi_health_url, secret_token)
144
207
 
145
- # Check health for Browser
146
- if build_type in [SandboxType.BROWSER.value]:
147
- browser_healthy = check_health(steelapi_health_url, secret_token)
148
- else:
149
- browser_healthy = True
150
-
151
- if browser_healthy and fastapi_healthy:
152
- logging.info("Health checks passed.")
208
+ if fastapi_healthy:
209
+ logger.info("Health checks passed.")
153
210
  subprocess.run(
154
211
  ["docker", "commit", container_id, f"{image_name}"],
155
212
  check=True,
156
213
  )
157
- logging.info(
214
+ logger.info(
158
215
  f"Docker image {image_name} committed successfully.",
159
216
  )
160
217
  subprocess.run(["docker", "stop", container_id], check=True)
161
218
  subprocess.run(["docker", "rm", container_id], check=True)
162
219
  else:
163
- logging.error("Health checks failed.")
220
+ logger.error("Health checks failed.")
164
221
  subprocess.run(["docker", "stop", container_id], check=True)
165
222
 
166
223
  choice = input(
@@ -171,9 +228,9 @@ def build_image(build_type, dockerfile_path=None):
171
228
  ["docker", "rmi", "-f", f"{image_name}dev"],
172
229
  check=True,
173
230
  )
174
- logging.info(f"Dev image {image_name}dev deleted.")
231
+ logger.info(f"Dev image {image_name}dev deleted.")
175
232
  else:
176
- logging.info(f"Dev image {image_name}dev retained.")
233
+ logger.info(f"Dev image {image_name}dev retained.")
177
234
 
178
235
 
179
236
  def main():
@@ -182,8 +239,8 @@ def main():
182
239
  )
183
240
  parser.add_argument(
184
241
  "build_type",
242
+ nargs="?",
185
243
  default="base",
186
- choices=[x.value for x in SandboxType] + ["all"],
187
244
  help="Specify the build type to execute.",
188
245
  )
189
246
 
@@ -193,20 +250,48 @@ def main():
193
250
  help="Specify the path for the Dockerfile.",
194
251
  )
195
252
 
253
+ parser.add_argument(
254
+ "--extension",
255
+ action="append",
256
+ help="Path to a Python file or module name to load as an extension",
257
+ )
258
+
259
+ parser.add_argument(
260
+ "--platform",
261
+ default=get_platform(),
262
+ choices=DOCKER_PLATFORMS,
263
+ help="Specify target platform for Docker image (default: current "
264
+ f"system platform: {get_platform()})",
265
+ )
266
+
196
267
  args = parser.parse_args()
197
268
 
269
+ if args.extension:
270
+ for ext in args.extension:
271
+ logger.info(f"Loading extension: {ext}")
272
+ mod = dynamic_import(ext)
273
+ logger.info(f"Extension loaded: {mod.__name__}")
274
+
198
275
  if args.build_type == "all":
199
276
  # Only build the built-in images
200
277
  for build_type in [x.value for x in SandboxType.get_builtin_members()]:
201
278
  build_image(build_type)
202
279
  else:
280
+ assert args.build_type in [
281
+ x.value for x in SandboxType
282
+ ], f"Invalid build type: {args.build_type}"
283
+
203
284
  if args.build_type not in [
204
285
  x.value for x in SandboxType.get_builtin_members()
205
286
  ]:
206
287
  assert (
207
288
  args.dockerfile_path is not None
208
289
  ), "Dockerfile path is required for custom images"
209
- build_image(args.build_type, args.dockerfile_path)
290
+ build_image(
291
+ args.build_type,
292
+ args.dockerfile_path,
293
+ platform_choice=args.platform,
294
+ )
210
295
 
211
296
 
212
297
  if __name__ == "__main__":