From 1e7180339e636d1b73b4890618fbffc296b9a8bb Mon Sep 17 00:00:00 2001 From: OhYee Date: Wed, 21 Jan 2026 11:20:32 +0800 Subject: [PATCH 1/2] feat(sandbox): add custom sandbox support with template type and client methods MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .github/workflows/release.yml | 2 +- agentrun/sandbox/__sandbox_async_template.py | 31 +++++++++- agentrun/sandbox/custom_sandbox.py | 24 ++++++++ agentrun/sandbox/model.py | 8 +++ agentrun/sandbox/sandbox.py | 61 ++++++++++++++++++-- pyproject.toml | 4 +- 6 files changed, 118 insertions(+), 12 deletions(-) create mode 100644 agentrun/sandbox/custom_sandbox.py diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5b97899..a9a49fc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,7 +28,7 @@ jobs: echo "Extracted version: ${VERSION}" - name: Update pyproject.toml run: | - sed -i 's/version = "[^"]*"/version = "'${VERSION}'"/' pyproject.toml + sed -i 's/^version = "[^"]*"/version = "'${VERSION}'"/' pyproject.toml echo "Updated pyproject.toml version to ${VERSION}" - name: Update __version__ in __init__.py run: | diff --git a/agentrun/sandbox/__sandbox_async_template.py b/agentrun/sandbox/__sandbox_async_template.py index 016711c..734214f 100644 --- a/agentrun/sandbox/__sandbox_async_template.py +++ b/agentrun/sandbox/__sandbox_async_template.py @@ -23,6 +23,7 @@ from agentrun.sandbox.aio_sandbox import AioSandbox from agentrun.sandbox.browser_sandbox import BrowserSandbox from agentrun.sandbox.code_interpreter_sandbox import CodeInterpreterSandbox + from agentrun.sandbox.custom_sandbox import CustomSandbox from agentrun.sandbox.model import ( ListSandboxesInput, ListSandboxesOutput, @@ -117,6 +118,20 @@ async def create_async( ) -> "AioSandbox": ... + @classmethod + @overload + async def create_async( + cls, + template_type: Literal[TemplateType.CUSTOM], + template_name: Optional[str] = None, + sandbox_idle_timeout_seconds: Optional[int] = 600, + nas_config: Optional["NASConfig"] = None, + oss_mount_config: Optional["OSSMountConfig"] = None, + polar_fs_config: Optional["PolarFsConfig"] = None, + config: Optional[Config] = None, + ) -> "CustomSandbox": + ... + @classmethod async def create_async( cls, @@ -127,7 +142,12 @@ async def create_async( oss_mount_config: Optional["OSSMountConfig"] = None, polar_fs_config: Optional["PolarFsConfig"] = None, config: Optional[Config] = None, - ) -> Union["CodeInterpreterSandbox", "BrowserSandbox", "AioSandbox"]: + ) -> Union[ + "CodeInterpreterSandbox", + "BrowserSandbox", + "AioSandbox", + "CustomSandbox", + ]: if template_name is None: # todo 可以考虑为用户创建一个模板? @@ -142,6 +162,7 @@ async def create_async( from agentrun.sandbox.code_interpreter_sandbox import ( CodeInterpreterSandbox, ) + from agentrun.sandbox.custom_sandbox import CustomSandbox if template_type != template.template_type: raise ValueError( @@ -172,6 +193,10 @@ async def create_async( sandbox = AioSandbox.model_validate( base_sandbox.model_dump(by_alias=False) ) + elif template.template_type == TemplateType.CUSTOM: + sandbox = CustomSandbox.model_validate( + base_sandbox.model_dump(by_alias=False) + ) else: raise ValueError( f"template_type {template.template_type} is not supported" @@ -194,7 +219,7 @@ async def stop_by_id_async(cls, sandbox_id: str): if sandbox_id is None: raise ValueError("sandbox_id is required") # todo 后续适配后使用 stop() - return await cls.__get_client().delete_sandbox_async(sandbox_id) + return await cls.__get_client().stop_sandbox_async(sandbox_id) @classmethod async def delete_by_id_async(cls, sandbox_id: str): @@ -460,4 +485,4 @@ async def stop_async(self): if self.sandbox_id is None: raise ValueError("sandbox_id is required to stop a Sandbox") # todo 后续适配后使用 stop() - return await self.delete_by_id_async(self.sandbox_id) + return await self.stop_by_id_async(self.sandbox_id) diff --git a/agentrun/sandbox/custom_sandbox.py b/agentrun/sandbox/custom_sandbox.py new file mode 100644 index 0000000..41aa3da --- /dev/null +++ b/agentrun/sandbox/custom_sandbox.py @@ -0,0 +1,24 @@ +from typing import Optional + +from agentrun.sandbox.model import TemplateType +from agentrun.utils.config import Config +from agentrun.utils.data_api import DataAPI, ResourceType + +from .sandbox import Sandbox + + +class CustomSandbox(Sandbox): + """Custom Sandbox""" + + _template_type = TemplateType.CUSTOM + + def get_base_url(self, config: Optional[Config] = None): + """Get CDP WebSocket URL for browser automation.""" + api = DataAPI( + resource_name="", + resource_type=ResourceType.Template, + namespace="sandboxes", + config=config, + ) + + return api.with_path("") diff --git a/agentrun/sandbox/model.py b/agentrun/sandbox/model.py index c662771..f79e1b2 100644 --- a/agentrun/sandbox/model.py +++ b/agentrun/sandbox/model.py @@ -34,6 +34,8 @@ class TemplateType(str, Enum): """浏览器 / Browser""" AIO = "AllInOne" """All-in-One 沙箱 / All-in-One Sandbox""" + CUSTOM = "CustomImage" + """自定义镜像 / Custom Image""" class TemplateNetworkMode(str, Enum): @@ -218,6 +220,12 @@ class TemplateContainerConfiguration(BaseModel): """容器镜像地址 / Container Image Address""" command: Optional[List[str]] = None """容器启动命令 / Container Start Command""" + acr_instance_id: Optional[str] = None + """ACR 实例 ID / ACR Instance ID""" + image_registry_type: Optional[str] = None + """镜像注册表类型 / Image Registry Type""" + port: Optional[int] = None + """端口 / Port""" class TemplateMcpOptions(BaseModel): diff --git a/agentrun/sandbox/sandbox.py b/agentrun/sandbox/sandbox.py index dcffab0..3bf229a 100644 --- a/agentrun/sandbox/sandbox.py +++ b/agentrun/sandbox/sandbox.py @@ -33,6 +33,7 @@ from agentrun.sandbox.aio_sandbox import AioSandbox from agentrun.sandbox.browser_sandbox import BrowserSandbox from agentrun.sandbox.code_interpreter_sandbox import CodeInterpreterSandbox + from agentrun.sandbox.custom_sandbox import CustomSandbox from agentrun.sandbox.model import ( ListSandboxesInput, ListSandboxesOutput, @@ -169,6 +170,34 @@ def create( ) -> "AioSandbox": ... + @classmethod + @overload + async def create_async( + cls, + template_type: Literal[TemplateType.CUSTOM], + template_name: Optional[str] = None, + sandbox_idle_timeout_seconds: Optional[int] = 600, + nas_config: Optional["NASConfig"] = None, + oss_mount_config: Optional["OSSMountConfig"] = None, + polar_fs_config: Optional["PolarFsConfig"] = None, + config: Optional[Config] = None, + ) -> "CustomSandbox": + ... + + @classmethod + @overload + def create( + cls, + template_type: Literal[TemplateType.CUSTOM], + template_name: Optional[str] = None, + sandbox_idle_timeout_seconds: Optional[int] = 600, + nas_config: Optional["NASConfig"] = None, + oss_mount_config: Optional["OSSMountConfig"] = None, + polar_fs_config: Optional["PolarFsConfig"] = None, + config: Optional[Config] = None, + ) -> "CustomSandbox": + ... + @classmethod async def create_async( cls, @@ -179,7 +208,12 @@ async def create_async( oss_mount_config: Optional["OSSMountConfig"] = None, polar_fs_config: Optional["PolarFsConfig"] = None, config: Optional[Config] = None, - ) -> Union["CodeInterpreterSandbox", "BrowserSandbox", "AioSandbox"]: + ) -> Union[ + "CodeInterpreterSandbox", + "BrowserSandbox", + "AioSandbox", + "CustomSandbox", + ]: if template_name is None: # todo 可以考虑为用户创建一个模板? @@ -194,6 +228,7 @@ async def create_async( from agentrun.sandbox.code_interpreter_sandbox import ( CodeInterpreterSandbox, ) + from agentrun.sandbox.custom_sandbox import CustomSandbox if template_type != template.template_type: raise ValueError( @@ -224,6 +259,10 @@ async def create_async( sandbox = AioSandbox.model_validate( base_sandbox.model_dump(by_alias=False) ) + elif template.template_type == TemplateType.CUSTOM: + sandbox = CustomSandbox.model_validate( + base_sandbox.model_dump(by_alias=False) + ) else: raise ValueError( f"template_type {template.template_type} is not supported" @@ -242,7 +281,12 @@ def create( oss_mount_config: Optional["OSSMountConfig"] = None, polar_fs_config: Optional["PolarFsConfig"] = None, config: Optional[Config] = None, - ) -> Union["CodeInterpreterSandbox", "BrowserSandbox", "AioSandbox"]: + ) -> Union[ + "CodeInterpreterSandbox", + "BrowserSandbox", + "AioSandbox", + "CustomSandbox", + ]: if template_name is None: # todo 可以考虑为用户创建一个模板? @@ -257,6 +301,7 @@ def create( from agentrun.sandbox.code_interpreter_sandbox import ( CodeInterpreterSandbox, ) + from agentrun.sandbox.custom_sandbox import CustomSandbox if template_type != template.template_type: raise ValueError( @@ -287,6 +332,10 @@ def create( sandbox = AioSandbox.model_validate( base_sandbox.model_dump(by_alias=False) ) + elif template.template_type == TemplateType.CUSTOM: + sandbox = CustomSandbox.model_validate( + base_sandbox.model_dump(by_alias=False) + ) else: raise ValueError( f"template_type {template.template_type} is not supported" @@ -309,7 +358,7 @@ async def stop_by_id_async(cls, sandbox_id: str): if sandbox_id is None: raise ValueError("sandbox_id is required") # todo 后续适配后使用 stop() - return await cls.__get_client().delete_sandbox_async(sandbox_id) + return await cls.__get_client().stop_sandbox_async(sandbox_id) @classmethod def stop_by_id(cls, sandbox_id: str): @@ -325,7 +374,7 @@ def stop_by_id(cls, sandbox_id: str): if sandbox_id is None: raise ValueError("sandbox_id is required") # todo 后续适配后使用 stop() - return cls.__get_client().delete_sandbox(sandbox_id) + return cls.__get_client().stop_sandbox(sandbox_id) @classmethod async def delete_by_id_async(cls, sandbox_id: str): @@ -839,10 +888,10 @@ async def stop_async(self): if self.sandbox_id is None: raise ValueError("sandbox_id is required to stop a Sandbox") # todo 后续适配后使用 stop() - return await self.delete_by_id_async(self.sandbox_id) + return await self.stop_by_id_async(self.sandbox_id) def stop(self): if self.sandbox_id is None: raise ValueError("sandbox_id is required to stop a Sandbox") # todo 后续适配后使用 stop() - return self.delete_by_id(self.sandbox_id) + return self.stop_by_id(self.sandbox_id) diff --git a/pyproject.toml b/pyproject.toml index de1fbc7..66fa854 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,7 @@ dependencies = [ "litellm>=1.79.3", "alibabacloud-devs20230714>=2.4.1", "pydash>=8.0.5", - "alibabacloud-agentrun20250910>=5.2.0", + "alibabacloud-agentrun20250910>=5.3.1", "alibabacloud_tea_openapi>=0.4.2", "alibabacloud_bailian20231229>=2.6.2", "agentrun-mem0ai>=0.0.10", @@ -104,7 +104,7 @@ known_third_party = ["alibabacloud_tea_openapi", "alibabacloud_devs20230714", "a sections = ["FUTURE", "STDLIB", "THIRDPARTY", "FIRSTPARTY", "LOCALFOLDER"] [tool.mypy] -python_version = "0.0.15" +python_version = "3.10" exclude = "tests/" plugins = ["pydantic.mypy"] # Start with non-strict mode, and switch to strict mode later. From 67d4843093245cb890a61253436eed2b67101d1a Mon Sep 17 00:00:00 2001 From: OhYee Date: Wed, 21 Jan 2026 12:04:19 +0800 Subject: [PATCH 2/2] Update agentrun/sandbox/custom_sandbox.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: OhYee --- agentrun/sandbox/custom_sandbox.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agentrun/sandbox/custom_sandbox.py b/agentrun/sandbox/custom_sandbox.py index 41aa3da..5e8eadd 100644 --- a/agentrun/sandbox/custom_sandbox.py +++ b/agentrun/sandbox/custom_sandbox.py @@ -13,7 +13,7 @@ class CustomSandbox(Sandbox): _template_type = TemplateType.CUSTOM def get_base_url(self, config: Optional[Config] = None): - """Get CDP WebSocket URL for browser automation.""" + """Get the base URL for the custom sandbox template.""" api = DataAPI( resource_name="", resource_type=ResourceType.Template,