|
|
@@ -1,418 +0,0 @@
|
|
|
-"""
|
|
|
-Sandbox Tools (Async)
|
|
|
-通过 HTTP 异步调用沙盒管理服务的客户端库。
|
|
|
-"""
|
|
|
-
|
|
|
-import json
|
|
|
-import httpx
|
|
|
-from typing import Optional, List, Dict, Any
|
|
|
-
|
|
|
-from agent.tools import tool, ToolResult, ToolContext
|
|
|
-
|
|
|
-
|
|
|
-# 服务地址,可根据实际部署情况修改
|
|
|
-# SANDBOX_SERVER_URL = "http://192.168.100.20:9998"
|
|
|
-SANDBOX_SERVER_URL = "http://61.48.133.26:9998"
|
|
|
-
|
|
|
-# 默认超时时间(秒)
|
|
|
-DEFAULT_TIMEOUT = 300.0
|
|
|
-
|
|
|
-
|
|
|
-@tool(
|
|
|
- hidden_params=["context"],
|
|
|
- display={
|
|
|
- "zh": {
|
|
|
- "name": "创建沙盒环境",
|
|
|
- "params": {
|
|
|
- "image": "Docker 镜像",
|
|
|
- "mem_limit": "内存限制",
|
|
|
- "nano_cpus": "CPU 限制",
|
|
|
- "ports": "端口列表",
|
|
|
- "use_gpu": "启用 GPU",
|
|
|
- "gpu_count": "GPU 数量"
|
|
|
- }
|
|
|
- },
|
|
|
- "en": {
|
|
|
- "name": "Create Sandbox",
|
|
|
- "params": {
|
|
|
- "image": "Docker image",
|
|
|
- "mem_limit": "Memory limit",
|
|
|
- "nano_cpus": "CPU limit",
|
|
|
- "ports": "Port list",
|
|
|
- "use_gpu": "Enable GPU",
|
|
|
- "gpu_count": "GPU count"
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-)
|
|
|
-async def sandbox_create_environment(
|
|
|
- image: str = "agent-sandbox:latest",
|
|
|
- mem_limit: str = "512m",
|
|
|
- nano_cpus: int = 500000000,
|
|
|
- ports: Optional[List[int]] = None,
|
|
|
- use_gpu: bool = False,
|
|
|
- gpu_count: int = -1,
|
|
|
- server_url: str = None,
|
|
|
- timeout: float = DEFAULT_TIMEOUT,
|
|
|
- context: Optional[ToolContext] = None,
|
|
|
-) -> ToolResult:
|
|
|
- """
|
|
|
- 创建一个隔离的 Docker 开发环境。
|
|
|
-
|
|
|
- Args:
|
|
|
- image: Docker 镜像名称,默认为 "agent-sandbox:latest"。
|
|
|
- 可以使用其他镜像如 "python:3.12-slim", "node:18-slim" 等。
|
|
|
- mem_limit: 容器最大内存限制,默认为 "512m"。
|
|
|
- nano_cpus: 容器最大 CPU 限制(纳秒),默认为 500000000(0.5 CPU)。
|
|
|
- ports: 需要映射的端口列表,如 [8080, 3000]。
|
|
|
- use_gpu: 是否启用 GPU 支持,默认为 False。需要宿主机安装 nvidia-container-toolkit。
|
|
|
- gpu_count: 使用的 GPU 数量,-1 表示使用所有可用 GPU,默认为 -1。
|
|
|
- server_url: 服务地址,默认使用全局配置 SANDBOX_SERVER_URL。
|
|
|
- timeout: 请求超时时间(秒),默认 300 秒。
|
|
|
- context: 工具上下文
|
|
|
-
|
|
|
- Returns:
|
|
|
- ToolResult: 包含沙盒创建结果
|
|
|
- """
|
|
|
- url = f"{server_url or SANDBOX_SERVER_URL}/api/create_environment"
|
|
|
- payload = {
|
|
|
- "image": image,
|
|
|
- "mem_limit": mem_limit,
|
|
|
- "nano_cpus": nano_cpus,
|
|
|
- "use_gpu": use_gpu,
|
|
|
- "gpu_count": gpu_count
|
|
|
- }
|
|
|
- if ports:
|
|
|
- payload["ports"] = ports
|
|
|
-
|
|
|
- try:
|
|
|
- async with httpx.AsyncClient(timeout=timeout) as client:
|
|
|
- response = await client.post(url, json=payload)
|
|
|
- response.raise_for_status()
|
|
|
- data = response.json()
|
|
|
-
|
|
|
- sandbox_id = data.get("sandbox_id", "")
|
|
|
- port_mapping = data.get("port_mapping", {})
|
|
|
- access_urls = data.get("access_urls", [])
|
|
|
-
|
|
|
- output_parts = [f"沙盒 ID: {sandbox_id}"]
|
|
|
- if port_mapping:
|
|
|
- output_parts.append(f"端口映射: {json.dumps(port_mapping)}")
|
|
|
- if access_urls:
|
|
|
- output_parts.append(f"访问地址: {', '.join(access_urls)}")
|
|
|
-
|
|
|
- return ToolResult(
|
|
|
- title="沙盒环境创建成功",
|
|
|
- output="\n".join(output_parts),
|
|
|
- metadata=data
|
|
|
- )
|
|
|
- except httpx.HTTPStatusError as e:
|
|
|
- return ToolResult(
|
|
|
- title="沙盒创建失败",
|
|
|
- output=f"HTTP 错误: {e.response.status_code}",
|
|
|
- error=str(e)
|
|
|
- )
|
|
|
- except httpx.RequestError as e:
|
|
|
- return ToolResult(
|
|
|
- title="沙盒创建失败",
|
|
|
- output=f"网络请求失败: {str(e)}",
|
|
|
- error=str(e)
|
|
|
- )
|
|
|
-
|
|
|
-
|
|
|
-@tool(
|
|
|
- hidden_params=["context"],
|
|
|
- display={
|
|
|
- "zh": {
|
|
|
- "name": "执行沙盒命令",
|
|
|
- "params": {
|
|
|
- "sandbox_id": "沙盒 ID",
|
|
|
- "command": "Shell 命令",
|
|
|
- "is_background": "后台执行",
|
|
|
- "timeout": "超时时间"
|
|
|
- }
|
|
|
- },
|
|
|
- "en": {
|
|
|
- "name": "Run Shell in Sandbox",
|
|
|
- "params": {
|
|
|
- "sandbox_id": "Sandbox ID",
|
|
|
- "command": "Shell command",
|
|
|
- "is_background": "Run in background",
|
|
|
- "timeout": "Timeout"
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-)
|
|
|
-async def sandbox_run_shell(
|
|
|
- sandbox_id: str,
|
|
|
- command: str,
|
|
|
- is_background: bool = False,
|
|
|
- timeout: int = 120,
|
|
|
- server_url: str = None,
|
|
|
- request_timeout: float = DEFAULT_TIMEOUT,
|
|
|
- context: Optional[ToolContext] = None,
|
|
|
-) -> ToolResult:
|
|
|
- """
|
|
|
- 在指定的沙盒中执行 Shell 命令。
|
|
|
-
|
|
|
- Args:
|
|
|
- sandbox_id: 沙盒 ID,由 create_environment 返回。
|
|
|
- command: 要执行的 Shell 命令,如 "pip install flask" 或 "python app.py"。
|
|
|
- is_background: 是否后台执行,默认为 False。
|
|
|
- - False:前台执行,等待命令完成并返回输出
|
|
|
- - True:后台执行,适合启动长期运行的服务
|
|
|
- timeout: 前台命令的超时时间(秒),默认 120 秒。后台命令不受此限制。
|
|
|
- server_url: 服务地址,默认使用全局配置 SANDBOX_SERVER_URL。
|
|
|
- request_timeout: HTTP 请求超时时间(秒),默认 300 秒。
|
|
|
- context: 工具上下文
|
|
|
-
|
|
|
- Returns:
|
|
|
- ToolResult: 命令执行结果
|
|
|
- """
|
|
|
- url = f"{server_url or SANDBOX_SERVER_URL}/api/run_shell"
|
|
|
- payload = {
|
|
|
- "sandbox_id": sandbox_id,
|
|
|
- "command": command,
|
|
|
- "is_background": is_background,
|
|
|
- "timeout": timeout
|
|
|
- }
|
|
|
-
|
|
|
- try:
|
|
|
- async with httpx.AsyncClient(timeout=request_timeout) as client:
|
|
|
- response = await client.post(url, json=payload)
|
|
|
- response.raise_for_status()
|
|
|
- data = response.json()
|
|
|
-
|
|
|
- if is_background:
|
|
|
- status = data.get("status", "")
|
|
|
- message = data.get("message", "")
|
|
|
- log_file = data.get("log_file", "")
|
|
|
- output = f"状态: {status}\n消息: {message}"
|
|
|
- if log_file:
|
|
|
- output += f"\n日志文件: {log_file}"
|
|
|
- return ToolResult(
|
|
|
- title=f"后台命令已启动: {command[:50]}",
|
|
|
- output=output,
|
|
|
- metadata=data
|
|
|
- )
|
|
|
- else:
|
|
|
- exit_code = data.get("exit_code", -1)
|
|
|
- stdout = data.get("stdout", "")
|
|
|
- stderr = data.get("stderr", "")
|
|
|
-
|
|
|
- output_parts = []
|
|
|
- if stdout:
|
|
|
- output_parts.append(stdout)
|
|
|
- if stderr:
|
|
|
- if output_parts:
|
|
|
- output_parts.append("\n--- stderr ---")
|
|
|
- output_parts.append(stderr)
|
|
|
- if not output_parts:
|
|
|
- output_parts.append("(命令无输出)")
|
|
|
-
|
|
|
- success = exit_code == 0
|
|
|
- title = f"命令: {command[:50]}"
|
|
|
- if not success:
|
|
|
- title += f" (exit code: {exit_code})"
|
|
|
-
|
|
|
- return ToolResult(
|
|
|
- title=title,
|
|
|
- output="\n".join(output_parts),
|
|
|
- metadata=data,
|
|
|
- error=None if success else f"Command failed with exit code {exit_code}"
|
|
|
- )
|
|
|
- except httpx.HTTPStatusError as e:
|
|
|
- return ToolResult(
|
|
|
- title="命令执行失败",
|
|
|
- output=f"HTTP 错误: {e.response.status_code}",
|
|
|
- error=str(e)
|
|
|
- )
|
|
|
- except httpx.RequestError as e:
|
|
|
- return ToolResult(
|
|
|
- title="命令执行失败",
|
|
|
- output=f"网络请求失败: {str(e)}",
|
|
|
- error=str(e)
|
|
|
- )
|
|
|
-
|
|
|
-
|
|
|
-@tool(
|
|
|
- hidden_params=["context"],
|
|
|
- display={
|
|
|
- "zh": {
|
|
|
- "name": "重建沙盒端口",
|
|
|
- "params": {
|
|
|
- "sandbox_id": "沙盒 ID",
|
|
|
- "ports": "端口列表",
|
|
|
- "mem_limit": "内存限制",
|
|
|
- "nano_cpus": "CPU 限制",
|
|
|
- "use_gpu": "启用 GPU",
|
|
|
- "gpu_count": "GPU 数量"
|
|
|
- }
|
|
|
- },
|
|
|
- "en": {
|
|
|
- "name": "Rebuild Sandbox Ports",
|
|
|
- "params": {
|
|
|
- "sandbox_id": "Sandbox ID",
|
|
|
- "ports": "Port list",
|
|
|
- "mem_limit": "Memory limit",
|
|
|
- "nano_cpus": "CPU limit",
|
|
|
- "use_gpu": "Enable GPU",
|
|
|
- "gpu_count": "GPU count"
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-)
|
|
|
-async def sandbox_rebuild_with_ports(
|
|
|
- sandbox_id: str,
|
|
|
- ports: List[int],
|
|
|
- mem_limit: str = "1g",
|
|
|
- nano_cpus: int = 1000000000,
|
|
|
- use_gpu: bool = False,
|
|
|
- gpu_count: int = -1,
|
|
|
- server_url: str = None,
|
|
|
- timeout: float = DEFAULT_TIMEOUT,
|
|
|
- context: Optional[ToolContext] = None,
|
|
|
-) -> ToolResult:
|
|
|
- """
|
|
|
- 重建沙盒并应用新的端口映射。
|
|
|
-
|
|
|
- 使用场景:先创建沙盒克隆项目,阅读 README 后才知道需要暴露哪些端口,
|
|
|
- 此时调用此函数重建沙盒,应用正确的端口映射。
|
|
|
-
|
|
|
- 注意:重建后会返回新的 sandbox_id,后续操作需要使用新 ID。
|
|
|
- 容器内的所有文件(克隆的代码、安装的依赖等)都会保留。
|
|
|
-
|
|
|
- Args:
|
|
|
- sandbox_id: 当前沙盒 ID。
|
|
|
- ports: 需要映射的端口列表,如 [8080, 3306, 6379]。
|
|
|
- mem_limit: 容器最大内存限制,默认为 "1g"。
|
|
|
- nano_cpus: 容器最大 CPU 限制(纳秒),默认为 1000000000(1 CPU)。
|
|
|
- use_gpu: 是否启用 GPU 支持,默认为 False。需要宿主机安装 nvidia-container-toolkit。
|
|
|
- gpu_count: 使用的 GPU 数量,-1 表示使用所有可用 GPU,默认为 -1。
|
|
|
- server_url: 服务地址,默认使用全局配置 SANDBOX_SERVER_URL。
|
|
|
- timeout: 请求超时时间(秒),默认 300 秒。
|
|
|
- context: 工具上下文
|
|
|
-
|
|
|
- Returns:
|
|
|
- ToolResult: 重建结果
|
|
|
- """
|
|
|
- url = f"{server_url or SANDBOX_SERVER_URL}/api/rebuild_with_ports"
|
|
|
- payload = {
|
|
|
- "sandbox_id": sandbox_id,
|
|
|
- "ports": ports,
|
|
|
- "mem_limit": mem_limit,
|
|
|
- "nano_cpus": nano_cpus,
|
|
|
- "use_gpu": use_gpu,
|
|
|
- "gpu_count": gpu_count
|
|
|
- }
|
|
|
-
|
|
|
- try:
|
|
|
- async with httpx.AsyncClient(timeout=timeout) as client:
|
|
|
- response = await client.post(url, json=payload)
|
|
|
- response.raise_for_status()
|
|
|
- data = response.json()
|
|
|
-
|
|
|
- old_id = data.get("old_sandbox_id", "")
|
|
|
- new_id = data.get("new_sandbox_id", "")
|
|
|
- port_mapping = data.get("port_mapping", {})
|
|
|
- access_urls = data.get("access_urls", [])
|
|
|
-
|
|
|
- output_parts = [
|
|
|
- f"旧沙盒 ID: {old_id} (已销毁)",
|
|
|
- f"新沙盒 ID: {new_id}"
|
|
|
- ]
|
|
|
- if port_mapping:
|
|
|
- output_parts.append(f"端口映射: {json.dumps(port_mapping)}")
|
|
|
- if access_urls:
|
|
|
- output_parts.append(f"访问地址: {', '.join(access_urls)}")
|
|
|
-
|
|
|
- return ToolResult(
|
|
|
- title="沙盒重建成功",
|
|
|
- output="\n".join(output_parts),
|
|
|
- metadata=data
|
|
|
- )
|
|
|
- except httpx.HTTPStatusError as e:
|
|
|
- return ToolResult(
|
|
|
- title="沙盒重建失败",
|
|
|
- output=f"HTTP 错误: {e.response.status_code}",
|
|
|
- error=str(e)
|
|
|
- )
|
|
|
- except httpx.RequestError as e:
|
|
|
- return ToolResult(
|
|
|
- title="沙盒重建失败",
|
|
|
- output=f"网络请求失败: {str(e)}",
|
|
|
- error=str(e)
|
|
|
- )
|
|
|
-
|
|
|
-
|
|
|
-@tool(
|
|
|
- requires_confirmation=True,
|
|
|
- display={
|
|
|
- "zh": {
|
|
|
- "name": "销毁沙盒环境",
|
|
|
- "params": {
|
|
|
- "sandbox_id": "沙盒 ID"
|
|
|
- }
|
|
|
- },
|
|
|
- "en": {
|
|
|
- "name": "Destroy Sandbox",
|
|
|
- "params": {
|
|
|
- "sandbox_id": "Sandbox ID"
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-)
|
|
|
-async def sandbox_destroy_environment(
|
|
|
- sandbox_id: str,
|
|
|
- server_url: str = None,
|
|
|
- timeout: float = DEFAULT_TIMEOUT,
|
|
|
- context: Optional[ToolContext] = None,
|
|
|
-) -> ToolResult:
|
|
|
- """
|
|
|
- 销毁沙盒环境,释放资源。
|
|
|
-
|
|
|
- Args:
|
|
|
- sandbox_id: 沙盒 ID。
|
|
|
- server_url: 服务地址,默认使用全局配置 SANDBOX_SERVER_URL。
|
|
|
- timeout: 请求超时时间(秒),默认 300 秒。
|
|
|
- context: 工具上下文
|
|
|
-
|
|
|
- Returns:
|
|
|
- ToolResult: 销毁结果
|
|
|
- """
|
|
|
- url = f"{server_url or SANDBOX_SERVER_URL}/api/destroy_environment"
|
|
|
- payload = {
|
|
|
- "sandbox_id": sandbox_id
|
|
|
- }
|
|
|
-
|
|
|
- try:
|
|
|
- async with httpx.AsyncClient(timeout=timeout) as client:
|
|
|
- response = await client.post(url, json=payload)
|
|
|
- response.raise_for_status()
|
|
|
- data = response.json()
|
|
|
-
|
|
|
- status = data.get("status", "")
|
|
|
- message = data.get("message", "")
|
|
|
- removed_tools = data.get("removed_tools", [])
|
|
|
-
|
|
|
- output_parts = [f"状态: {status}", f"消息: {message}"]
|
|
|
- if removed_tools:
|
|
|
- output_parts.append(f"已移除的工具: {', '.join(removed_tools)}")
|
|
|
-
|
|
|
- return ToolResult(
|
|
|
- title="沙盒环境已销毁",
|
|
|
- output="\n".join(output_parts),
|
|
|
- metadata=data
|
|
|
- )
|
|
|
- except httpx.HTTPStatusError as e:
|
|
|
- return ToolResult(
|
|
|
- title="沙盒销毁失败",
|
|
|
- output=f"HTTP 错误: {e.response.status_code}",
|
|
|
- error=str(e)
|
|
|
- )
|
|
|
- except httpx.RequestError as e:
|
|
|
- return ToolResult(
|
|
|
- title="沙盒销毁失败",
|
|
|
- output=f"网络请求失败: {str(e)}",
|
|
|
- error=str(e)
|
|
|
- )
|