Skip to content

Commit 1e71803

Browse files
committed
feat(sandbox): add custom sandbox support with template type and client methods
Add support for custom sandbox templates with new TemplateType.CUSTOM enum, implement CustomSandbox creation and validation, update union types to include CustomSandbox, and modify container configuration model with ACR instance and registry type fields. Also fix release workflow regex pattern and update alibabacloud-agentrun dependency. feat: 添加自定义沙箱支持和模板类型及客户端方法 添加对自定义沙箱模板的支持,新增 TemplateType.CUSTOM 枚举, 实现 CustomSandbox 创建和验证,更新联合类型包含 CustomSandbox, 并修改容器配置模型增加 ACR 实例和注册表类型字段。 同时修复发布工作流正则表达式模式并更新 alibabacloud-agentrun 依赖。 Change-Id: Ieb1e39c1acd49a43f54f22c113704a5046ab0458 Signed-off-by: OhYee <oyohyee@oyohyee.com>
1 parent 394ce58 commit 1e71803

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 CDP WebSocket URL for browser automation."""
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)