agentscope-runtime 0.1.3__py3-none-any.whl → 0.1.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.
- agentscope_runtime/engine/agents/agentscope_agent/agent.py +56 -12
- agentscope_runtime/engine/agents/agentscope_agent/hooks.py +2 -1
- agentscope_runtime/engine/agents/agno_agent.py +11 -5
- agentscope_runtime/engine/agents/autogen_agent.py +10 -4
- agentscope_runtime/engine/agents/utils.py +53 -0
- agentscope_runtime/engine/services/context_manager.py +2 -0
- agentscope_runtime/engine/services/mem0_memory_service.py +124 -0
- agentscope_runtime/engine/services/memory_service.py +2 -1
- agentscope_runtime/engine/services/redis_session_history_service.py +4 -3
- agentscope_runtime/engine/services/sandbox_service.py +6 -16
- agentscope_runtime/engine/services/session_history_service.py +4 -3
- agentscope_runtime/engine/services/tablestore_memory_service.py +304 -0
- agentscope_runtime/engine/services/tablestore_rag_service.py +143 -0
- agentscope_runtime/engine/services/tablestore_session_history_service.py +293 -0
- agentscope_runtime/engine/services/utils/__init__.py +0 -0
- agentscope_runtime/engine/services/utils/tablestore_service_utils.py +352 -0
- agentscope_runtime/sandbox/box/base/base_sandbox.py +2 -2
- agentscope_runtime/sandbox/box/browser/browser_sandbox.py +2 -2
- agentscope_runtime/sandbox/box/filesystem/filesystem_sandbox.py +2 -2
- agentscope_runtime/sandbox/box/training_box/training_box.py +4 -12
- agentscope_runtime/sandbox/build.py +37 -17
- agentscope_runtime/sandbox/client/http_client.py +42 -10
- agentscope_runtime/sandbox/client/training_client.py +0 -1
- agentscope_runtime/sandbox/constant.py +26 -0
- agentscope_runtime/sandbox/custom/custom_sandbox.py +5 -5
- agentscope_runtime/sandbox/custom/example.py +2 -2
- agentscope_runtime/sandbox/manager/collections/in_memory_mapping.py +4 -2
- agentscope_runtime/sandbox/manager/collections/redis_mapping.py +25 -9
- agentscope_runtime/sandbox/manager/container_clients/__init__.py +0 -10
- agentscope_runtime/sandbox/manager/container_clients/agentrun_client.py +1096 -0
- agentscope_runtime/sandbox/manager/container_clients/docker_client.py +25 -201
- agentscope_runtime/sandbox/manager/container_clients/kubernetes_client.py +1 -3
- agentscope_runtime/sandbox/manager/sandbox_manager.py +40 -13
- agentscope_runtime/sandbox/manager/server/app.py +27 -0
- agentscope_runtime/sandbox/manager/server/config.py +30 -2
- agentscope_runtime/sandbox/model/container.py +1 -1
- agentscope_runtime/sandbox/model/manager_config.py +93 -5
- agentscope_runtime/sandbox/utils.py +97 -0
- agentscope_runtime/version.py +1 -1
- {agentscope_runtime-0.1.3.dist-info → agentscope_runtime-0.1.5.dist-info}/METADATA +56 -57
- {agentscope_runtime-0.1.3.dist-info → agentscope_runtime-0.1.5.dist-info}/RECORD +45 -40
- agentscope_runtime/engine/agents/llm_agent.py +0 -51
- agentscope_runtime/engine/llms/__init__.py +0 -3
- agentscope_runtime/engine/llms/base_llm.py +0 -60
- agentscope_runtime/engine/llms/qwen_llm.py +0 -47
- {agentscope_runtime-0.1.3.dist-info → agentscope_runtime-0.1.5.dist-info}/WHEEL +0 -0
- {agentscope_runtime-0.1.3.dist-info → agentscope_runtime-0.1.5.dist-info}/entry_points.txt +0 -0
- {agentscope_runtime-0.1.3.dist-info → agentscope_runtime-0.1.5.dist-info}/licenses/LICENSE +0 -0
- {agentscope_runtime-0.1.3.dist-info → agentscope_runtime-0.1.5.dist-info}/top_level.txt +0 -0
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
import traceback
|
|
3
3
|
import logging
|
|
4
|
-
import platform
|
|
5
4
|
import socket
|
|
6
|
-
import subprocess
|
|
7
5
|
|
|
8
6
|
import docker
|
|
9
7
|
|
|
10
8
|
from .base_client import BaseClient
|
|
11
|
-
from ..collections import
|
|
9
|
+
from ..collections import (
|
|
10
|
+
RedisSetCollection,
|
|
11
|
+
InMemorySetCollection,
|
|
12
|
+
RedisMapping,
|
|
13
|
+
InMemoryMapping,
|
|
14
|
+
)
|
|
12
15
|
|
|
13
16
|
|
|
14
17
|
logger = logging.getLogger(__name__)
|
|
@@ -34,138 +37,6 @@ def is_port_available(port):
|
|
|
34
37
|
return False
|
|
35
38
|
|
|
36
39
|
|
|
37
|
-
def sweep_port(port):
|
|
38
|
-
"""Sweep all processes found listening on a given port.
|
|
39
|
-
|
|
40
|
-
Args:
|
|
41
|
-
port (int): The port number.
|
|
42
|
-
|
|
43
|
-
Returns:
|
|
44
|
-
bool: True if successful, False if failed.
|
|
45
|
-
"""
|
|
46
|
-
try:
|
|
47
|
-
system = platform.system().lower()
|
|
48
|
-
if system == "windows":
|
|
49
|
-
return _sweep_port_windows(port)
|
|
50
|
-
else:
|
|
51
|
-
return _sweep_port_unix(port)
|
|
52
|
-
except Exception as e:
|
|
53
|
-
logger.error(
|
|
54
|
-
f"An error occurred while killing processes on port {port}: {e}",
|
|
55
|
-
)
|
|
56
|
-
return False
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
def _sweep_port_unix(port):
|
|
60
|
-
"""
|
|
61
|
-
Sweep all processes found listening on a given port.
|
|
62
|
-
|
|
63
|
-
Args:
|
|
64
|
-
port (int): The port number.
|
|
65
|
-
|
|
66
|
-
Returns:
|
|
67
|
-
int: Number of processes swept (terminated).
|
|
68
|
-
"""
|
|
69
|
-
try:
|
|
70
|
-
# Use lsof to find the processes using the port
|
|
71
|
-
# TODO: support windows
|
|
72
|
-
result = subprocess.run(
|
|
73
|
-
["lsof", "-i", f":{port}"],
|
|
74
|
-
capture_output=True,
|
|
75
|
-
text=True,
|
|
76
|
-
check=True,
|
|
77
|
-
)
|
|
78
|
-
|
|
79
|
-
# Parse the output
|
|
80
|
-
lines = result.stdout.strip().split("\n")
|
|
81
|
-
if len(lines) <= 1:
|
|
82
|
-
# No process is using the port
|
|
83
|
-
return True
|
|
84
|
-
|
|
85
|
-
# Iterate over each line (excluding the header) and kill each process
|
|
86
|
-
killed_count = 0
|
|
87
|
-
for line in lines[1:]:
|
|
88
|
-
parts = line.split()
|
|
89
|
-
if len(parts) > 1:
|
|
90
|
-
pid = parts[1]
|
|
91
|
-
|
|
92
|
-
# Kill the process using the PID
|
|
93
|
-
subprocess.run(["kill", "-9", pid], check=False)
|
|
94
|
-
killed_count += 1
|
|
95
|
-
|
|
96
|
-
if not is_port_available(port):
|
|
97
|
-
logger.warning(
|
|
98
|
-
f"Port {port} is still in use after killing processes.",
|
|
99
|
-
)
|
|
100
|
-
|
|
101
|
-
return True
|
|
102
|
-
|
|
103
|
-
except Exception as e:
|
|
104
|
-
logger.error(
|
|
105
|
-
f"An error occurred while killing processes on port {port}: {e}",
|
|
106
|
-
)
|
|
107
|
-
return False
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
def _sweep_port_windows(port):
|
|
111
|
-
"""
|
|
112
|
-
Windows implementation using netstat and taskkill
|
|
113
|
-
"""
|
|
114
|
-
try:
|
|
115
|
-
# Use netstat to find the processes using the port
|
|
116
|
-
result = subprocess.run(
|
|
117
|
-
["netstat", "-ano"],
|
|
118
|
-
capture_output=True,
|
|
119
|
-
text=True,
|
|
120
|
-
check=True,
|
|
121
|
-
)
|
|
122
|
-
|
|
123
|
-
# Parse the output to find processes using the specific port
|
|
124
|
-
lines = result.stdout.strip().split("\n")
|
|
125
|
-
pids_to_kill = set()
|
|
126
|
-
|
|
127
|
-
for line in lines:
|
|
128
|
-
if f":{port}" in line and "LISTENING" in line:
|
|
129
|
-
parts = line.split()
|
|
130
|
-
if len(parts) >= 5:
|
|
131
|
-
pid = parts[-1] # PID is usually the last column
|
|
132
|
-
if pid.isdigit(): # Ensure it's a valid PID
|
|
133
|
-
pids_to_kill.add(pid)
|
|
134
|
-
|
|
135
|
-
if not pids_to_kill:
|
|
136
|
-
return True
|
|
137
|
-
|
|
138
|
-
# Kill the processes
|
|
139
|
-
killed_count = 0
|
|
140
|
-
for pid in pids_to_kill:
|
|
141
|
-
try:
|
|
142
|
-
result = subprocess.run(
|
|
143
|
-
["taskkill", "/PID", pid, "/F"],
|
|
144
|
-
capture_output=True,
|
|
145
|
-
text=True,
|
|
146
|
-
check=False,
|
|
147
|
-
)
|
|
148
|
-
if result.returncode == 0:
|
|
149
|
-
killed_count += 1
|
|
150
|
-
except Exception as e:
|
|
151
|
-
logger.debug(f"Failed to kill process {pid}: {e}")
|
|
152
|
-
continue
|
|
153
|
-
|
|
154
|
-
if not is_port_available(port):
|
|
155
|
-
logger.warning(
|
|
156
|
-
f"Port {port} is still in use after killing processes.",
|
|
157
|
-
)
|
|
158
|
-
|
|
159
|
-
return True
|
|
160
|
-
|
|
161
|
-
except subprocess.CalledProcessError as e:
|
|
162
|
-
logger.error(f"netstat command failed: {e}")
|
|
163
|
-
return False
|
|
164
|
-
except Exception as e:
|
|
165
|
-
logger.error(f"Error in Windows port sweep: {e}")
|
|
166
|
-
return False
|
|
167
|
-
|
|
168
|
-
|
|
169
40
|
class DockerClient(BaseClient):
|
|
170
41
|
def __init__(self, config=None):
|
|
171
42
|
self.config = config
|
|
@@ -193,8 +64,13 @@ class DockerClient(BaseClient):
|
|
|
193
64
|
redis_client,
|
|
194
65
|
set_name=self.config.redis_port_key,
|
|
195
66
|
)
|
|
67
|
+
self.ports_cache = RedisMapping(
|
|
68
|
+
redis_client,
|
|
69
|
+
prefix=self.config.redis_port_key,
|
|
70
|
+
)
|
|
196
71
|
else:
|
|
197
72
|
self.port_set = InMemorySetCollection()
|
|
73
|
+
self.ports_cache = InMemoryMapping()
|
|
198
74
|
|
|
199
75
|
try:
|
|
200
76
|
self.client = docker.from_env()
|
|
@@ -208,40 +84,6 @@ class DockerClient(BaseClient):
|
|
|
208
84
|
"export DOCKER_HOST=unix://$HOME/.colima/docker.sock",
|
|
209
85
|
) from e
|
|
210
86
|
|
|
211
|
-
def _try_pull_from_acr(self, image):
|
|
212
|
-
"""
|
|
213
|
-
Attempt to pull the image from the Alibaba Cloud Container Registry
|
|
214
|
-
(ACR) and retag it.
|
|
215
|
-
"""
|
|
216
|
-
try:
|
|
217
|
-
acr_registry = "agentscope-registry.ap-southeast-1.cr.aliyuncs.com"
|
|
218
|
-
acr_image = f"{acr_registry}/{image}"
|
|
219
|
-
|
|
220
|
-
logger.info(
|
|
221
|
-
f"Attempting to pull from ACR: {acr_image}, it might take "
|
|
222
|
-
f"several minutes.",
|
|
223
|
-
)
|
|
224
|
-
self.client.images.pull(acr_image)
|
|
225
|
-
logger.info(f"Successfully pulled image from ACR: {acr_image}")
|
|
226
|
-
|
|
227
|
-
# Retag the image
|
|
228
|
-
acr_img_obj = self.client.images.get(acr_image)
|
|
229
|
-
acr_img_obj.tag(image)
|
|
230
|
-
logger.debug(f"Successfully tagged image as: {image}")
|
|
231
|
-
|
|
232
|
-
# Optionally remove the original tag to save space
|
|
233
|
-
try:
|
|
234
|
-
self.client.images.remove(acr_image)
|
|
235
|
-
logger.debug(f"Removed original tag: {acr_image}")
|
|
236
|
-
except Exception as e:
|
|
237
|
-
logger.debug(f"Failed to remove original tag: {e}")
|
|
238
|
-
return True
|
|
239
|
-
except Exception as e:
|
|
240
|
-
logger.error(
|
|
241
|
-
f"Failed to pull from ACR: {e}, {traceback.format_exc()}",
|
|
242
|
-
)
|
|
243
|
-
return False
|
|
244
|
-
|
|
245
87
|
def create(
|
|
246
88
|
self,
|
|
247
89
|
image,
|
|
@@ -279,22 +121,11 @@ class DockerClient(BaseClient):
|
|
|
279
121
|
)
|
|
280
122
|
self.client.images.pull(image)
|
|
281
123
|
logger.debug(
|
|
282
|
-
f"Image '{image}' successfully pulled
|
|
283
|
-
f"registry.",
|
|
284
|
-
)
|
|
285
|
-
pull_success = True
|
|
286
|
-
except docker.errors.APIError as e:
|
|
287
|
-
logger.warning(
|
|
288
|
-
f"Failed to pull from default registry: {e}",
|
|
124
|
+
f"Image '{image}' successfully pulled.",
|
|
289
125
|
)
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
pull_success = self._try_pull_from_acr(image)
|
|
293
|
-
|
|
294
|
-
if not pull_success:
|
|
126
|
+
except Exception as e:
|
|
295
127
|
logger.error(
|
|
296
|
-
f"Failed to pull image '{image}'
|
|
297
|
-
f"default and ACR",
|
|
128
|
+
f"Failed to pull image '{image}': {str(e)}",
|
|
298
129
|
)
|
|
299
130
|
return None, None, None
|
|
300
131
|
|
|
@@ -314,6 +145,9 @@ class DockerClient(BaseClient):
|
|
|
314
145
|
)
|
|
315
146
|
container.reload()
|
|
316
147
|
_id = container.id
|
|
148
|
+
|
|
149
|
+
self.ports_cache.set(_id, list(port_mapping.values()))
|
|
150
|
+
|
|
317
151
|
return _id, list(port_mapping.values()), "localhost"
|
|
318
152
|
except Exception as e:
|
|
319
153
|
logger.error(f"An error occurred: {e}, {traceback.format_exc()}")
|
|
@@ -326,16 +160,6 @@ class DockerClient(BaseClient):
|
|
|
326
160
|
container_id,
|
|
327
161
|
)
|
|
328
162
|
|
|
329
|
-
# Check whether the ports are occupied by other processes
|
|
330
|
-
port_mapping = container.attrs["NetworkSettings"]["Ports"]
|
|
331
|
-
for _, mappings in port_mapping.items():
|
|
332
|
-
if mappings is not None:
|
|
333
|
-
for mapping in mappings:
|
|
334
|
-
host_port = int(mapping["HostPort"])
|
|
335
|
-
if is_port_available(host_port):
|
|
336
|
-
continue
|
|
337
|
-
sweep_port(host_port["HostPort"])
|
|
338
|
-
|
|
339
163
|
container.start()
|
|
340
164
|
return True
|
|
341
165
|
except Exception as e:
|
|
@@ -360,16 +184,16 @@ class DockerClient(BaseClient):
|
|
|
360
184
|
container = self.client.containers.get(
|
|
361
185
|
container_id,
|
|
362
186
|
)
|
|
363
|
-
|
|
364
|
-
|
|
187
|
+
ports = self.ports_cache.get(container_id)
|
|
188
|
+
self.ports_cache.delete(container_id)
|
|
189
|
+
|
|
190
|
+
# Remove container
|
|
365
191
|
container.remove(force=force)
|
|
366
192
|
|
|
367
|
-
#
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
host_port = int(mapping["HostPort"])
|
|
372
|
-
self.port_set.remove(host_port)
|
|
193
|
+
# Remove ports
|
|
194
|
+
if ports:
|
|
195
|
+
for host_port in ports:
|
|
196
|
+
self.port_set.remove(host_port)
|
|
373
197
|
|
|
374
198
|
return True
|
|
375
199
|
except Exception as e:
|
|
@@ -92,11 +92,9 @@ class KubernetesClient(BaseClient):
|
|
|
92
92
|
container_name = name or "main-container"
|
|
93
93
|
|
|
94
94
|
# Container specification
|
|
95
|
-
# TODO: use image from docker registry first
|
|
96
95
|
container = client.V1Container(
|
|
97
96
|
name=container_name,
|
|
98
|
-
image=
|
|
99
|
-
f"/{image}",
|
|
97
|
+
image=image,
|
|
100
98
|
image_pull_policy=runtime_config.get(
|
|
101
99
|
"image_pull_policy",
|
|
102
100
|
"IfNotPresent",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
-
# pylint: disable=redefined-outer-name, protected-access
|
|
2
|
+
# pylint: disable=redefined-outer-name, protected-access
|
|
3
|
+
# pylint: disable=too-many-branches, too-many-statements
|
|
3
4
|
import logging
|
|
4
5
|
import os
|
|
5
6
|
import json
|
|
@@ -14,6 +15,8 @@ from urllib.parse import urlparse, urlunparse
|
|
|
14
15
|
import shortuuid
|
|
15
16
|
import requests
|
|
16
17
|
|
|
18
|
+
from .container_clients.docker_client import DockerClient
|
|
19
|
+
from .container_clients.kubernetes_client import KubernetesClient
|
|
17
20
|
from ..model import (
|
|
18
21
|
ContainerModel,
|
|
19
22
|
SandboxManagerEnvConfig,
|
|
@@ -32,7 +35,6 @@ from ..manager.storage import (
|
|
|
32
35
|
LocalStorage,
|
|
33
36
|
OSSStorage,
|
|
34
37
|
)
|
|
35
|
-
from ..manager.container_clients import DockerClient, KubernetesClient
|
|
36
38
|
from ..constant import BROWSER_SESSION_ID
|
|
37
39
|
|
|
38
40
|
logging.basicConfig(level=logging.INFO)
|
|
@@ -119,6 +121,7 @@ class SandboxManager:
|
|
|
119
121
|
self.pool_size = self.config.pool_size
|
|
120
122
|
self.prefix = self.config.container_prefix_key
|
|
121
123
|
self.default_mount_dir = self.config.default_mount_dir
|
|
124
|
+
self.readonly_mounts = self.config.readonly_mounts
|
|
122
125
|
self.storage_folder = (
|
|
123
126
|
self.config.storage_folder or self.default_mount_dir
|
|
124
127
|
)
|
|
@@ -157,6 +160,10 @@ class SandboxManager:
|
|
|
157
160
|
self.client = DockerClient(config=self.config)
|
|
158
161
|
elif self.container_deployment == "k8s":
|
|
159
162
|
self.client = KubernetesClient(config=self.config)
|
|
163
|
+
elif self.container_deployment == "agentrun":
|
|
164
|
+
from .container_clients.agentrun_client import AgentRunClient
|
|
165
|
+
|
|
166
|
+
self.client = AgentRunClient(config=self.config)
|
|
160
167
|
else:
|
|
161
168
|
raise NotImplementedError("Not implemented")
|
|
162
169
|
else:
|
|
@@ -380,7 +387,7 @@ class SandboxManager:
|
|
|
380
387
|
def create(
|
|
381
388
|
self,
|
|
382
389
|
sandbox_type=None,
|
|
383
|
-
mount_dir=None,
|
|
390
|
+
mount_dir=None, # TODO: remove to avoid leaking
|
|
384
391
|
storage_path=None,
|
|
385
392
|
environment: Optional[Dict] = None,
|
|
386
393
|
):
|
|
@@ -422,7 +429,7 @@ class SandboxManager:
|
|
|
422
429
|
mount_dir = os.path.join(self.default_mount_dir, session_id)
|
|
423
430
|
os.makedirs(mount_dir, exist_ok=True)
|
|
424
431
|
|
|
425
|
-
if mount_dir:
|
|
432
|
+
if mount_dir and self.container_deployment != "agentrun":
|
|
426
433
|
if not os.path.isabs(mount_dir):
|
|
427
434
|
mount_dir = os.path.abspath(mount_dir)
|
|
428
435
|
|
|
@@ -433,7 +440,11 @@ class SandboxManager:
|
|
|
433
440
|
session_id,
|
|
434
441
|
)
|
|
435
442
|
|
|
436
|
-
if
|
|
443
|
+
if (
|
|
444
|
+
mount_dir
|
|
445
|
+
and storage_path
|
|
446
|
+
and self.container_deployment != "agentrun"
|
|
447
|
+
):
|
|
437
448
|
self.storage.download_folder(storage_path, mount_dir)
|
|
438
449
|
|
|
439
450
|
try:
|
|
@@ -448,7 +459,7 @@ class SandboxManager:
|
|
|
448
459
|
runtime_token = secrets.token_hex(16)
|
|
449
460
|
|
|
450
461
|
# Prepare volume bindings if a mount directory is provided
|
|
451
|
-
if mount_dir:
|
|
462
|
+
if mount_dir and self.container_deployment != "agentrun":
|
|
452
463
|
volume_bindings = {
|
|
453
464
|
mount_dir: {
|
|
454
465
|
"bind": self.workdir,
|
|
@@ -458,7 +469,16 @@ class SandboxManager:
|
|
|
458
469
|
else:
|
|
459
470
|
volume_bindings = {}
|
|
460
471
|
|
|
461
|
-
|
|
472
|
+
if self.readonly_mounts:
|
|
473
|
+
for host_path, container_path in self.readonly_mounts.items():
|
|
474
|
+
if not os.path.isabs(host_path):
|
|
475
|
+
host_path = os.path.abspath(host_path)
|
|
476
|
+
volume_bindings[host_path] = {
|
|
477
|
+
"bind": container_path,
|
|
478
|
+
"mode": "ro",
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
_id, ports, ip, *rest = self.client.create(
|
|
462
482
|
image,
|
|
463
483
|
name=container_name,
|
|
464
484
|
ports=["80/tcp"], # Nginx
|
|
@@ -470,6 +490,12 @@ class SandboxManager:
|
|
|
470
490
|
runtime_config=config.runtime_config,
|
|
471
491
|
)
|
|
472
492
|
|
|
493
|
+
http_protocol = "http"
|
|
494
|
+
ws_protocol = "ws"
|
|
495
|
+
if rest and rest[0] == "https":
|
|
496
|
+
http_protocol = "https"
|
|
497
|
+
ws_protocol = "wss"
|
|
498
|
+
|
|
473
499
|
if _id is None:
|
|
474
500
|
return None
|
|
475
501
|
|
|
@@ -487,22 +513,23 @@ class SandboxManager:
|
|
|
487
513
|
session_id=session_id,
|
|
488
514
|
container_id=_id,
|
|
489
515
|
container_name=container_name,
|
|
490
|
-
base_url=f"
|
|
491
|
-
browser_url=f"
|
|
516
|
+
base_url=f"{http_protocol}://{ip}:{ports[0]}/fastapi",
|
|
517
|
+
browser_url=f"{http_protocol}://{ip}:{ports[0]}/steel-api"
|
|
492
518
|
f"/{runtime_token}",
|
|
493
|
-
front_browser_ws=f"
|
|
519
|
+
front_browser_ws=f"{ws_protocol}://{ip}:"
|
|
494
520
|
f"{ports[0]}/steel-api/"
|
|
495
521
|
f"{runtime_token}/v1/sessions/cast",
|
|
496
|
-
client_browser_ws=f"
|
|
522
|
+
client_browser_ws=f"{ws_protocol}://{ip}:"
|
|
497
523
|
f"{ports[0]}/steel-api/{runtime_token}/&sessionId"
|
|
498
524
|
f"={BROWSER_SESSION_ID}",
|
|
499
|
-
artifacts_sio=f"
|
|
525
|
+
artifacts_sio=f"{http_protocol}://{ip}:{ports[0]}/v1",
|
|
500
526
|
ports=[ports[0]],
|
|
501
527
|
mount_dir=str(mount_dir),
|
|
502
528
|
storage_path=storage_path,
|
|
503
529
|
runtime_token=runtime_token,
|
|
504
530
|
version=image,
|
|
505
531
|
)
|
|
532
|
+
|
|
506
533
|
# Register in mapping
|
|
507
534
|
self.container_mapping.set(
|
|
508
535
|
container_model.container_name,
|
|
@@ -632,7 +659,7 @@ class SandboxManager:
|
|
|
632
659
|
self._generate_container_key(identity),
|
|
633
660
|
)
|
|
634
661
|
if container_model is None:
|
|
635
|
-
|
|
662
|
+
raise RuntimeError(f"No container found with id: {identity}.")
|
|
636
663
|
if hasattr(container_model, "model_dump_json"):
|
|
637
664
|
container_model = container_model.model_dump_json()
|
|
638
665
|
|
|
@@ -20,6 +20,7 @@ from ...manager.server.models import (
|
|
|
20
20
|
)
|
|
21
21
|
from ...manager.sandbox_manager import SandboxManager
|
|
22
22
|
from ...model.manager_config import SandboxManagerEnvConfig
|
|
23
|
+
from ...utils import dynamic_import
|
|
23
24
|
from ....version import __version__
|
|
24
25
|
|
|
25
26
|
# Configure logging
|
|
@@ -61,6 +62,7 @@ def get_config() -> SandboxManagerEnvConfig:
|
|
|
61
62
|
redis_enabled=settings.REDIS_ENABLED,
|
|
62
63
|
container_deployment=settings.CONTAINER_DEPLOYMENT,
|
|
63
64
|
default_mount_dir=settings.DEFAULT_MOUNT_DIR,
|
|
65
|
+
readonly_mounts=settings.READONLY_MOUNTS,
|
|
64
66
|
storage_folder=settings.STORAGE_FOLDER,
|
|
65
67
|
port_range=settings.PORT_RANGE,
|
|
66
68
|
pool_size=settings.POOL_SIZE,
|
|
@@ -77,6 +79,18 @@ def get_config() -> SandboxManagerEnvConfig:
|
|
|
77
79
|
redis_container_pool_key=settings.REDIS_CONTAINER_POOL_KEY,
|
|
78
80
|
k8s_namespace=settings.K8S_NAMESPACE,
|
|
79
81
|
kubeconfig_path=settings.KUBECONFIG_PATH,
|
|
82
|
+
agent_run_access_key_id=settings.AGENT_RUN_ACCESS_KEY_ID,
|
|
83
|
+
agent_run_access_key_secret=settings.AGENT_RUN_ACCESS_KEY_SECRET,
|
|
84
|
+
agent_run_account_id=settings.AGENT_RUN_ACCOUNT_ID,
|
|
85
|
+
agent_run_region_id=settings.AGENT_RUN_REGION_ID,
|
|
86
|
+
agent_run_cpu=settings.AGENT_RUN_CPU,
|
|
87
|
+
agent_run_memory=settings.AGENT_RUN_MEMORY,
|
|
88
|
+
agent_run_vpc_id=settings.AGENT_RUN_VPC_ID,
|
|
89
|
+
agent_run_vswitch_ids=settings.AGENT_RUN_VSWITCH_IDS,
|
|
90
|
+
agent_run_security_group_id=settings.AGENT_RUN_SECURITY_GROUP_ID,
|
|
91
|
+
agent_run_prefix=settings.AGENT_RUN_PREFIX,
|
|
92
|
+
agentrun_log_project=settings.AGENT_RUN_LOG_PROJECT,
|
|
93
|
+
agentrun_log_store=settings.AGENT_RUN_LOG_STORE,
|
|
80
94
|
)
|
|
81
95
|
return _config
|
|
82
96
|
|
|
@@ -311,8 +325,21 @@ def main():
|
|
|
311
325
|
default="INFO",
|
|
312
326
|
help="Set the logging level (default: INFO)",
|
|
313
327
|
)
|
|
328
|
+
|
|
329
|
+
parser.add_argument(
|
|
330
|
+
"--extension",
|
|
331
|
+
action="append",
|
|
332
|
+
help="Path to a Python file or module name to load as an extension",
|
|
333
|
+
)
|
|
334
|
+
|
|
314
335
|
args = parser.parse_args()
|
|
315
336
|
|
|
337
|
+
if args.extension:
|
|
338
|
+
for ext in args.extension:
|
|
339
|
+
logger.info(f"Loading extension: {ext}")
|
|
340
|
+
mod = dynamic_import(ext)
|
|
341
|
+
logger.info(f"Extension loaded: {mod.__name__}")
|
|
342
|
+
|
|
316
343
|
# Setup logging based on command line argument
|
|
317
344
|
setup_logging(args.log_level)
|
|
318
345
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
import os
|
|
3
|
-
from typing import Optional, Tuple, Literal
|
|
3
|
+
from typing import Optional, Tuple, Literal, Dict
|
|
4
4
|
from pydantic_settings import BaseSettings
|
|
5
5
|
from pydantic import field_validator, ConfigDict
|
|
6
6
|
from dotenv import load_dotenv
|
|
@@ -21,8 +21,18 @@ class Settings(BaseSettings):
|
|
|
21
21
|
POOL_SIZE: int = 1
|
|
22
22
|
AUTO_CLEANUP: bool = True
|
|
23
23
|
CONTAINER_PREFIX_KEY: str = "runtime_sandbox_container_"
|
|
24
|
-
CONTAINER_DEPLOYMENT: Literal[
|
|
24
|
+
CONTAINER_DEPLOYMENT: Literal[
|
|
25
|
+
"docker",
|
|
26
|
+
"cloud",
|
|
27
|
+
"k8s",
|
|
28
|
+
"agentrun",
|
|
29
|
+
] = "docker"
|
|
25
30
|
DEFAULT_MOUNT_DIR: str = "sessions_mount_dir"
|
|
31
|
+
# Read-only mounts (host_path -> container_path)
|
|
32
|
+
# Example in .env:
|
|
33
|
+
# READONLY_MOUNTS={"\/opt\/shared": "\/mnt\/shared", "\/etc\/timezone":
|
|
34
|
+
# "\/etc\/timezone"}
|
|
35
|
+
READONLY_MOUNTS: Optional[Dict[str, str]] = None
|
|
26
36
|
STORAGE_FOLDER: str = "runtime_sandbox_storage"
|
|
27
37
|
PORT_RANGE: Tuple[int, int] = (49152, 59152)
|
|
28
38
|
|
|
@@ -47,6 +57,24 @@ class Settings(BaseSettings):
|
|
|
47
57
|
K8S_NAMESPACE: str = "default"
|
|
48
58
|
KUBECONFIG_PATH: Optional[str] = None
|
|
49
59
|
|
|
60
|
+
# AgentRun settings
|
|
61
|
+
AGENT_RUN_ACCOUNT_ID: Optional[str] = None
|
|
62
|
+
AGENT_RUN_ACCESS_KEY_ID: Optional[str] = None
|
|
63
|
+
AGENT_RUN_ACCESS_KEY_SECRET: Optional[str] = None
|
|
64
|
+
AGENT_RUN_REGION_ID: str = "cn-hangzhou"
|
|
65
|
+
|
|
66
|
+
AGENT_RUN_CPU: float = 2.0
|
|
67
|
+
AGENT_RUN_MEMORY: int = 2048
|
|
68
|
+
|
|
69
|
+
AGENT_RUN_VPC_ID: Optional[str] = None
|
|
70
|
+
AGENT_RUN_VSWITCH_IDS: Optional[list[str]] = None
|
|
71
|
+
AGENT_RUN_SECURITY_GROUP_ID: Optional[str] = None
|
|
72
|
+
|
|
73
|
+
AGENT_RUN_PREFIX: str = "agentscope-sandbox"
|
|
74
|
+
|
|
75
|
+
AGENT_RUN_LOG_PROJECT: Optional[str] = None
|
|
76
|
+
AGENT_RUN_LOG_STORE: Optional[str] = None
|
|
77
|
+
|
|
50
78
|
model_config = ConfigDict(
|
|
51
79
|
case_sensitive=True,
|
|
52
80
|
extra="allow",
|