"""沙箱执行请求 / 响应模型,与 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