Skip to content

Commit 0590ae2

Browse files
authored
Merge pull request #39 from Serverless-Devs/feat-custom-sandbox
feat(sandbox): add custom sandbox support with template type and clie…
2 parents 394ce58 + 67d4843 commit 0590ae2

File tree

6 files changed

+118
-12
lines changed

6 files changed

+118
-12
lines changed

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ jobs:
2828
echo "Extracted version: ${VERSION}"
2929
- name: Update pyproject.toml
3030
run: |
31-
sed -i 's/version = "[^"]*"/version = "'${VERSION}'"/' pyproject.toml
31+
sed -i 's/^version = "[^"]*"/version = "'${VERSION}'"/' pyproject.toml
3232
echo "Updated pyproject.toml version to ${VERSION}"
3333
- name: Update __version__ in __init__.py
3434
run: |

agentrun/sandbox/__sandbox_async_template.py

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from agentrun.sandbox.aio_sandbox import AioSandbox
2424
from agentrun.sandbox.browser_sandbox import BrowserSandbox
2525
from agentrun.sandbox.code_interpreter_sandbox import CodeInterpreterSandbox
26+
from agentrun.sandbox.custom_sandbox import CustomSandbox
2627
from agentrun.sandbox.model import (
2728
ListSandboxesInput,
2829
ListSandboxesOutput,
@@ -117,6 +118,20 @@ async def create_async(
117118
) -> "AioSandbox":
118119
...
119120

121+
@classmethod
122+
@overload
123+
async def create_async(
124+
cls,
125+
template_type: Literal[TemplateType.CUSTOM],
126+
template_name: Optional[str] = None,
127+
sandbox_idle_timeout_seconds: Optional[int] = 600,
128+
nas_config: Optional["NASConfig"] = None,
129+
oss_mount_config: Optional["OSSMountConfig"] = None,
130+
polar_fs_config: Optional["PolarFsConfig"] = None,
131+
config: Optional[Config] = None,
132+
) -> "CustomSandbox":
133+
...
134+
120135
@classmethod
121136
async def create_async(
122137
cls,
@@ -127,7 +142,12 @@ async def create_async(
127142
oss_mount_config: Optional["OSSMountConfig"] = None,
128143
polar_fs_config: Optional["PolarFsConfig"] = None,
129144
config: Optional[Config] = None,
130-
) -> Union["CodeInterpreterSandbox", "BrowserSandbox", "AioSandbox"]:
145+
) -> Union[
146+
"CodeInterpreterSandbox",
147+
"BrowserSandbox",
148+
"AioSandbox",
149+
"CustomSandbox",
150+
]:
131151

132152
if template_name is None:
133153
# todo 可以考虑为用户创建一个模板?
@@ -142,6 +162,7 @@ async def create_async(
142162
from agentrun.sandbox.code_interpreter_sandbox import (
143163
CodeInterpreterSandbox,
144164
)
165+
from agentrun.sandbox.custom_sandbox import CustomSandbox
145166

146167
if template_type != template.template_type:
147168
raise ValueError(
@@ -172,6 +193,10 @@ async def create_async(
172193
sandbox = AioSandbox.model_validate(
173194
base_sandbox.model_dump(by_alias=False)
174195
)
196+
elif template.template_type == TemplateType.CUSTOM:
197+
sandbox = CustomSandbox.model_validate(
198+
base_sandbox.model_dump(by_alias=False)
199+
)
175200
else:
176201
raise ValueError(
177202
f"template_type {template.template_type} is not supported"
@@ -194,7 +219,7 @@ async def stop_by_id_async(cls, sandbox_id: str):
194219
if sandbox_id is None:
195220
raise ValueError("sandbox_id is required")
196221
# todo 后续适配后使用 stop()
197-
return await cls.__get_client().delete_sandbox_async(sandbox_id)
222+
return await cls.__get_client().stop_sandbox_async(sandbox_id)
198223

199224
@classmethod
200225
async def delete_by_id_async(cls, sandbox_id: str):
@@ -460,4 +485,4 @@ async def stop_async(self):
460485
if self.sandbox_id is None:
461486
raise ValueError("sandbox_id is required to stop a Sandbox")
462487
# todo 后续适配后使用 stop()
463-
return await self.delete_by_id_async(self.sandbox_id)
488+
return await self.stop_by_id_async(self.sandbox_id)

agentrun/sandbox/custom_sandbox.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from typing import Optional
2+
3+
from agentrun.sandbox.model import TemplateType
4+
from agentrun.utils.config import Config
5+
from agentrun.utils.data_api import DataAPI, ResourceType
6+
7+
from .sandbox import Sandbox
8+
9+
10+
class CustomSandbox(Sandbox):
11+
"""Custom Sandbox"""
12+
13+
_template_type = TemplateType.CUSTOM
14+
15+
def get_base_url(self, config: Optional[Config] = None):
16+
"""Get the base URL for the custom sandbox template."""
17+
api = DataAPI(
18+
resource_name="",
19+
resource_type=ResourceType.Template,
20+
namespace="sandboxes",
21+
config=config,
22+
)
23+
24+
return api.with_path("")

agentrun/sandbox/model.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ class TemplateType(str, Enum):
3434
"""浏览器 / Browser"""
3535
AIO = "AllInOne"
3636
"""All-in-One 沙箱 / All-in-One Sandbox"""
37+
CUSTOM = "CustomImage"
38+
"""自定义镜像 / Custom Image"""
3739

3840

3941
class TemplateNetworkMode(str, Enum):
@@ -218,6 +220,12 @@ class TemplateContainerConfiguration(BaseModel):
218220
"""容器镜像地址 / Container Image Address"""
219221
command: Optional[List[str]] = None
220222
"""容器启动命令 / Container Start Command"""
223+
acr_instance_id: Optional[str] = None
224+
"""ACR 实例 ID / ACR Instance ID"""
225+
image_registry_type: Optional[str] = None
226+
"""镜像注册表类型 / Image Registry Type"""
227+
port: Optional[int] = None
228+
"""端口 / Port"""
221229

222230

223231
class TemplateMcpOptions(BaseModel):

agentrun/sandbox/sandbox.py

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
from agentrun.sandbox.aio_sandbox import AioSandbox
3434
from agentrun.sandbox.browser_sandbox import BrowserSandbox
3535
from agentrun.sandbox.code_interpreter_sandbox import CodeInterpreterSandbox
36+
from agentrun.sandbox.custom_sandbox import CustomSandbox
3637
from agentrun.sandbox.model import (
3738
ListSandboxesInput,
3839
ListSandboxesOutput,
@@ -169,6 +170,34 @@ def create(
169170
) -> "AioSandbox":
170171
...
171172

173+
@classmethod
174+
@overload
175+
async def create_async(
176+
cls,
177+
template_type: Literal[TemplateType.CUSTOM],
178+
template_name: Optional[str] = None,
179+
sandbox_idle_timeout_seconds: Optional[int] = 600,
180+
nas_config: Optional["NASConfig"] = None,
181+
oss_mount_config: Optional["OSSMountConfig"] = None,
182+
polar_fs_config: Optional["PolarFsConfig"] = None,
183+
config: Optional[Config] = None,
184+
) -> "CustomSandbox":
185+
...
186+
187+
@classmethod
188+
@overload
189+
def create(
190+
cls,
191+
template_type: Literal[TemplateType.CUSTOM],
192+
template_name: Optional[str] = None,
193+
sandbox_idle_timeout_seconds: Optional[int] = 600,
194+
nas_config: Optional["NASConfig"] = None,
195+
oss_mount_config: Optional["OSSMountConfig"] = None,
196+
polar_fs_config: Optional["PolarFsConfig"] = None,
197+
config: Optional[Config] = None,
198+
) -> "CustomSandbox":
199+
...
200+
172201
@classmethod
173202
async def create_async(
174203
cls,
@@ -179,7 +208,12 @@ async def create_async(
179208
oss_mount_config: Optional["OSSMountConfig"] = None,
180209
polar_fs_config: Optional["PolarFsConfig"] = None,
181210
config: Optional[Config] = None,
182-
) -> Union["CodeInterpreterSandbox", "BrowserSandbox", "AioSandbox"]:
211+
) -> Union[
212+
"CodeInterpreterSandbox",
213+
"BrowserSandbox",
214+
"AioSandbox",
215+
"CustomSandbox",
216+
]:
183217

184218
if template_name is None:
185219
# todo 可以考虑为用户创建一个模板?
@@ -194,6 +228,7 @@ async def create_async(
194228
from agentrun.sandbox.code_interpreter_sandbox import (
195229
CodeInterpreterSandbox,
196230
)
231+
from agentrun.sandbox.custom_sandbox import CustomSandbox
197232

198233
if template_type != template.template_type:
199234
raise ValueError(
@@ -224,6 +259,10 @@ async def create_async(
224259
sandbox = AioSandbox.model_validate(
225260
base_sandbox.model_dump(by_alias=False)
226261
)
262+
elif template.template_type == TemplateType.CUSTOM:
263+
sandbox = CustomSandbox.model_validate(
264+
base_sandbox.model_dump(by_alias=False)
265+
)
227266
else:
228267
raise ValueError(
229268
f"template_type {template.template_type} is not supported"
@@ -242,7 +281,12 @@ def create(
242281
oss_mount_config: Optional["OSSMountConfig"] = None,
243282
polar_fs_config: Optional["PolarFsConfig"] = None,
244283
config: Optional[Config] = None,
245-
) -> Union["CodeInterpreterSandbox", "BrowserSandbox", "AioSandbox"]:
284+
) -> Union[
285+
"CodeInterpreterSandbox",
286+
"BrowserSandbox",
287+
"AioSandbox",
288+
"CustomSandbox",
289+
]:
246290

247291
if template_name is None:
248292
# todo 可以考虑为用户创建一个模板?
@@ -257,6 +301,7 @@ def create(
257301
from agentrun.sandbox.code_interpreter_sandbox import (
258302
CodeInterpreterSandbox,
259303
)
304+
from agentrun.sandbox.custom_sandbox import CustomSandbox
260305

261306
if template_type != template.template_type:
262307
raise ValueError(
@@ -287,6 +332,10 @@ def create(
287332
sandbox = AioSandbox.model_validate(
288333
base_sandbox.model_dump(by_alias=False)
289334
)
335+
elif template.template_type == TemplateType.CUSTOM:
336+
sandbox = CustomSandbox.model_validate(
337+
base_sandbox.model_dump(by_alias=False)
338+
)
290339
else:
291340
raise ValueError(
292341
f"template_type {template.template_type} is not supported"
@@ -309,7 +358,7 @@ async def stop_by_id_async(cls, sandbox_id: str):
309358
if sandbox_id is None:
310359
raise ValueError("sandbox_id is required")
311360
# todo 后续适配后使用 stop()
312-
return await cls.__get_client().delete_sandbox_async(sandbox_id)
361+
return await cls.__get_client().stop_sandbox_async(sandbox_id)
313362

314363
@classmethod
315364
def stop_by_id(cls, sandbox_id: str):
@@ -325,7 +374,7 @@ def stop_by_id(cls, sandbox_id: str):
325374
if sandbox_id is None:
326375
raise ValueError("sandbox_id is required")
327376
# todo 后续适配后使用 stop()
328-
return cls.__get_client().delete_sandbox(sandbox_id)
377+
return cls.__get_client().stop_sandbox(sandbox_id)
329378

330379
@classmethod
331380
async def delete_by_id_async(cls, sandbox_id: str):
@@ -839,10 +888,10 @@ async def stop_async(self):
839888
if self.sandbox_id is None:
840889
raise ValueError("sandbox_id is required to stop a Sandbox")
841890
# todo 后续适配后使用 stop()
842-
return await self.delete_by_id_async(self.sandbox_id)
891+
return await self.stop_by_id_async(self.sandbox_id)
843892

844893
def stop(self):
845894
if self.sandbox_id is None:
846895
raise ValueError("sandbox_id is required to stop a Sandbox")
847896
# todo 后续适配后使用 stop()
848-
return self.delete_by_id(self.sandbox_id)
897+
return self.stop_by_id(self.sandbox_id)

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ dependencies = [
1313
"litellm>=1.79.3",
1414
"alibabacloud-devs20230714>=2.4.1",
1515
"pydash>=8.0.5",
16-
"alibabacloud-agentrun20250910>=5.2.0",
16+
"alibabacloud-agentrun20250910>=5.3.1",
1717
"alibabacloud_tea_openapi>=0.4.2",
1818
"alibabacloud_bailian20231229>=2.6.2",
1919
"agentrun-mem0ai>=0.0.10",
@@ -104,7 +104,7 @@ known_third_party = ["alibabacloud_tea_openapi", "alibabacloud_devs20230714", "a
104104
sections = ["FUTURE", "STDLIB", "THIRDPARTY", "FIRSTPARTY", "LOCALFOLDER"]
105105

106106
[tool.mypy]
107-
python_version = "0.0.15"
107+
python_version = "3.10"
108108
exclude = "tests/"
109109
plugins = ["pydantic.mypy"]
110110
# Start with non-strict mode, and switch to strict mode later.

0 commit comments

Comments
 (0)