File size: 6,086 Bytes
fbd9d3d
 
e5e756a
 
fbd9d3d
 
e5e756a
 
fbd9d3d
e5e756a
fbd9d3d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e5e756a
fbd9d3d
 
cc826a1
fbd9d3d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e5e756a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fbd9d3d
 
 
 
e5e756a
fbd9d3d
 
 
 
 
 
 
e5e756a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fbd9d3d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
"""
沙盒MCP插件 API路由

支持文件列表、读写、上传、下载和命令执行。
"""

from fastapi import APIRouter, HTTPException, UploadFile, File, Query
from fastapi.responses import FileResponse
from typing import Optional
from pathlib import Path

router = APIRouter()

# 在运行时获取插件实例
plugin = None


def set_plugin_instance(plugin_instance):
    """由系统调用,注入插件实例"""
    global plugin
    plugin = plugin_instance


@router.get("/")
async def api_root():
    """API根路径"""
    return {"message": "沙盒MCP插件API", "status": "运行中"}


@router.get("/status")
async def get_status():
    """获取插件状态"""
    if plugin is None:
        return {
            "name": "sandbox",
            "enabled": False,
            "sandbox_root": "",
            "file_count": 0,
            "total_size": 0,
            "tools": [],
            "message": "插件未加载",
        }

    return plugin.get_status()


@router.get("/files")
async def list_files(path: str = ""):
    """列出文件和目录

    参数:
    - path: 目录相对路径(空表示沙盒根目录)
    """
    if plugin is None or not plugin.enabled:
        raise HTTPException(status_code=400, detail="插件未启用")

    result = plugin.file_ops.list_files(path)
    return result


@router.get("/read")
async def read_file(path: str, encoding: str = "utf-8"):
    """读取文件内容

    参数:
    - path: 文件相对路径
    - encoding: 文件编码(默认utf-8)
    """
    if plugin is None or not plugin.enabled:
        raise HTTPException(status_code=400, detail="插件未启用")

    result = plugin.file_ops.read_file(path, encoding)
    return result


@router.post("/write")
async def write_file(path: str, content: str, mode: str = "write"):
    """写入文件内容

    参数:
    - path: 文件相对路径
    - content: 文件内容
    - mode: 写入模式(write 或 append)
    """
    if plugin is None or not plugin.enabled:
        raise HTTPException(status_code=400, detail="插件未启用")

    result = plugin.file_ops.write_file(path, content, mode)
    return result


@router.post("/upload")
async def upload_file(file: UploadFile = File(...), path: str = Query("")):
    """上传文件到沙盒

    参数:
    - file: 上传的文件
    - path: 目标目录相对路径(空表示沙盒根目录)

    返回:
    {
        "success": true/false,
        "path": "上传后的相对路径",
        "size": 文件大小(字节),
        "error": "错误信息"
    }
    """
    if plugin is None or not plugin.enabled:
        raise HTTPException(status_code=400, detail="插件未启用")

    try:
        # 读取文件内容
        content = await file.read()
        file_size = len(content)

        # 验证文件大小
        size_validation = plugin.security.validate_file_size(file_size)
        if not size_validation["valid"]:
            return {
                "success": False,
                "error": size_validation["error"],
            }

        # 构建目标路径
        filename = file.filename or "uploaded_file"
        if path:
            target_path = f"{path}/{filename}"
        else:
            target_path = filename

        # 验证路径安全
        path_validation = plugin.security.validate_path(target_path)
        if not path_validation["safe"]:
            return {
                "success": False,
                "error": path_validation["error"],
            }

        # 写入文件
        target = Path(path_validation["path"])
        target.parent.mkdir(parents=True, exist_ok=True)
        target.write_bytes(content)

        return {
            "success": True,
            "path": target_path,
            "size": file_size,
        }

    except Exception as e:
        return {
            "success": False,
            "error": f"上传失败: {str(e)}",
        }


@router.get("/download")
async def download_file(path: str):
    """下载沙盒内的文件

    参数:
    - path: 文件相对路径

    返回:
    文件响应(如果成功)或错误 JSON
    """
    if plugin is None or not plugin.enabled:
        raise HTTPException(status_code=400, detail="插件未启用")

    try:
        # 验证路径安全
        path_validation = plugin.security.validate_path(path)
        if not path_validation["safe"]:
            raise HTTPException(status_code=400, detail=path_validation["error"])

        file_path = Path(path_validation["path"])

        # 检查文件是否存在
        if not file_path.exists():
            raise HTTPException(status_code=404, detail="文件不存在")

        if not file_path.is_file():
            raise HTTPException(status_code=400, detail="不能下载目录")

        # 返回文件响应
        return FileResponse(
            path=str(file_path),
            filename=file_path.name,
            media_type="application/octet-stream",
        )

    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"下载失败: {str(e)}")


@router.post("/execute")
async def execute_command(command: str, timeout: int = 30, cwd: str = ""):
    """执行命令

    参数:
    - command: 要执行的命令
    - timeout: 超时时间(秒)
    - cwd: 工作目录(相对路径)
    """
    if plugin is None or not plugin.enabled:
        raise HTTPException(status_code=400, detail="插件未启用")

    result = await plugin.command_exec.execute(command, timeout, cwd)
    return result


@router.post("/enable")
async def enable_plugin():
    """启用插件"""
    if plugin is None:
        raise HTTPException(status_code=400, detail="插件未加载")

    plugin.on_enable()
    return {"message": "插件已启用"}


@router.post("/disable")
async def disable_plugin():
    """禁用插件"""
    if plugin is None:
        raise HTTPException(status_code=400, detail="插件未加载")

    plugin.on_disable()
    return {"message": "插件已禁用"}