144 lines
3.5 KiB
Python
144 lines
3.5 KiB
Python
"""沙箱执行请求 / 响应模型,与 Rust 端 JSON 契约对齐。"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
||
|
||
|
||
class ExecRequest(BaseModel):
|
||
"""``POST /v1/exec`` 请求体。``argv``(命令式)与 ``code``+``language``(代码片段式)二选一。"""
|
||
|
||
model_config = ConfigDict(extra="forbid")
|
||
|
||
argv: list[str] | None = Field(None, min_length=1)
|
||
code: str | None = None
|
||
language: str | None = None # code 模式语言,默认 python
|
||
stdin: str | None = None
|
||
timeout_ms: int | None = None
|
||
workdir: str | None = None
|
||
env: dict[str, str] = Field(default_factory=dict)
|
||
max_output_bytes: int | None = None
|
||
trace_id: str | None = None
|
||
|
||
@model_validator(mode="after")
|
||
def _enforce_argv_code_mutex(self) -> ExecRequest:
|
||
has_argv = self.argv is not None
|
||
has_code = self.code is not None
|
||
if has_argv == has_code:
|
||
raise ValueError("必须且只能指定 argv 或 code 之一")
|
||
if has_code and self.language is None:
|
||
self.language = "python"
|
||
return self
|
||
|
||
|
||
class ExecResponse(BaseModel):
|
||
"""``POST /v1/exec`` 响应体。"""
|
||
|
||
model_config = ConfigDict(extra="ignore")
|
||
|
||
ok: bool
|
||
exit_code: int | None = None
|
||
stdout: str = ""
|
||
stderr: str = ""
|
||
duration_ms: int = 0
|
||
timeout: bool = False
|
||
cancelled: bool = False
|
||
truncated: bool = False
|
||
trace_id: str = ""
|
||
isolation_mode: str = "process" # 实际生效的隔离模式(process/container)
|
||
|
||
|
||
class CancelResponse(BaseModel):
|
||
"""``POST /v1/cancel`` 响应体。"""
|
||
|
||
model_config = ConfigDict(extra="ignore")
|
||
|
||
cancelled: bool
|
||
trace_id: str
|
||
|
||
|
||
# ---- 文件 IO(不经 shell,二进制安全;与 Rust fileio.rs 契约对齐) ----
|
||
|
||
|
||
class FileReadRequest(BaseModel):
|
||
"""``POST /v1/file/read`` 请求。``path`` 为 base 内相对路径。"""
|
||
|
||
model_config = ConfigDict(extra="forbid")
|
||
|
||
path: str
|
||
offset: int | None = None
|
||
limit: int | None = None
|
||
|
||
|
||
class FileReadResponse(BaseModel):
|
||
"""``POST /v1/file/read`` 响应。``content_b64`` 为 base64url-no-pad(二进制安全)。"""
|
||
|
||
model_config = ConfigDict(extra="ignore")
|
||
|
||
content_b64: str
|
||
size: int
|
||
offset: int
|
||
returned: int
|
||
truncated: bool
|
||
|
||
|
||
class FileWriteRequest(BaseModel):
|
||
"""``POST /v1/file/write`` 请求。``content_b64`` 为 base64url-no-pad。"""
|
||
|
||
model_config = ConfigDict(extra="forbid")
|
||
|
||
path: str
|
||
content_b64: str
|
||
|
||
|
||
class FileWriteResponse(BaseModel):
|
||
"""``POST /v1/file/write`` 响应。"""
|
||
|
||
model_config = ConfigDict(extra="ignore")
|
||
|
||
bytes_written: int
|
||
|
||
|
||
class FileListRequest(BaseModel):
|
||
"""``POST /v1/file/list`` 请求。``path`` 默认 base 根(``.``)。"""
|
||
|
||
model_config = ConfigDict(extra="forbid")
|
||
|
||
path: str = "."
|
||
|
||
|
||
class DirEntry(BaseModel):
|
||
"""目录条目。"""
|
||
|
||
model_config = ConfigDict(extra="ignore")
|
||
|
||
name: str
|
||
kind: str # "file" | "dir" | "other"
|
||
size: int
|
||
|
||
|
||
class FileListResponse(BaseModel):
|
||
"""``POST /v1/file/list`` 响应(条目按名字排序)。"""
|
||
|
||
model_config = ConfigDict(extra="ignore")
|
||
|
||
entries: list[DirEntry]
|
||
|
||
|
||
class FileStatRequest(BaseModel):
|
||
"""``POST /v1/file/stat`` 请求。"""
|
||
|
||
model_config = ConfigDict(extra="forbid")
|
||
|
||
path: str
|
||
|
||
|
||
class FileStatResponse(BaseModel):
|
||
"""``POST /v1/file/stat`` 响应。"""
|
||
|
||
model_config = ConfigDict(extra="ignore")
|
||
|
||
kind: str
|
||
size: int
|
||
modified_unix: int
|