Skip to main content

tiny_agent/sandbox/
mod.rs

1mod host;
2
3pub use async_trait::async_trait;
4pub use host::HostSandbox;
5use std::{fmt, sync::Arc};
6
7/// 命令输出的来源流。
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum OutStream {
10    Stdout,
11    Stderr,
12}
13
14/// 实时输出回调:命令执行期间每产出一块就被调用一次(同步,对齐 bashkit 的 `OutputCallback`)。
15/// 块不保证按行对齐;把所有块拼起来等于最终的 stdout/stderr。
16pub type OutputCallback = Arc<dyn Fn(OutStream, &str) + Send + Sync>;
17
18/// 一次命令执行的结果。
19#[derive(Debug, Clone)]
20pub struct ExecOutput {
21    pub stdout: String,
22    pub stderr: String,
23    pub exit_code: Option<i32>,
24}
25
26impl ExecOutput {
27    /// 退出码为 0 视为成功。
28    pub fn is_success(&self) -> bool {
29        self.exit_code == Some(0)
30    }
31}
32
33/// sandbox 操作可能产生的错误。
34#[derive(Debug)]
35pub enum SandboxError {
36    /// 底层 IO / 进程错误。
37    Io(String),
38    /// 该 sandbox 不支持此能力(例如不支持快照)。
39    Unsupported,
40    /// 其他执行错误。
41    Other(String),
42}
43
44impl fmt::Display for SandboxError {
45    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46        match self {
47            SandboxError::Io(e) => write!(f, "sandbox io error: {e}"),
48            SandboxError::Unsupported => {
49                write!(f, "operation not supported by this sandbox")
50            }
51            SandboxError::Other(e) => write!(f, "sandbox error: {e}"),
52        }
53    }
54}
55
56impl std::error::Error for SandboxError {}
57
58impl From<std::io::Error> for SandboxError {
59    fn from(e: std::io::Error) -> Self {
60        SandboxError::Io(e.to_string())
61    }
62}
63
64/// agent 工具的执行环境抽象。
65///
66/// 所有会触达"外部世界"(进程、文件系统)的工具都应通过 [`Sandbox::execute`]
67/// 走这一层,这样替换 sandbox 实现(真机 / bashkit / 容器)时工具代码无需改动。
68///
69/// agent 的执行环境抽象。
70///
71/// 一个 `Sandbox` 实例绑定一个会话:[`execute`](Sandbox::execute) 负责执行,
72/// [`save`](Sandbox::save) 持久化自身状态,[`fork`](Sandbox::fork) 把自身复制给另一个会话。
73///
74/// **框架完全不规定状态怎么序列化**:可能是一坨字节、一个容器 image id、一个远程
75/// 快照引用……都藏在实现内部,trait 不暴露任何序列化格式。不需要持久化的后端
76/// (如 [`HostSandbox`])用默认实现即可。
77///
78/// [`Sandbox::open`] 是按 session 打开 / 恢复 / 初始化执行环境的入口。
79/// 对无会话状态的实现(如 [`HostSandbox`]),可直接返回自己;对需要快照 / 挂载 /
80/// bootstrap 的实现,则在这里按 `session_id` 恢复出真正的 session-bound sandbox。
81#[async_trait]
82pub trait Sandbox: Send + Sync {
83    /// 打开或恢复某个 session 对应的 sandbox。实现应保持幂等:同一 session 多次 open
84    /// 应恢复到同一份状态,并且 bootstrap 逻辑不应重复破坏环境。
85    async fn open(self: Arc<Self>, session_id: &str) -> Result<Arc<dyn Sandbox>, SandboxError>;
86
87    /// 执行一条命令并返回结果。
88    ///
89    /// 文件读写等操作由各工具自行拼成命令(如 `cat`、`printf`),本层只负责执行。
90    ///
91    /// `on_output` 可选:传入则在执行期间每产出一块输出就回调一次(用于实时刷屏 / 增量收集);
92    /// 不关心实时输出的调用方传 `None`。无论是否流式,完整结果都在末尾通过返回值给出。
93    async fn execute(
94        &self,
95        command: &str,
96        on_output: Option<OutputCallback>,
97    ) -> Result<ExecOutput, SandboxError>;
98
99    /// 持久化自身当前状态。实例自己知道 session_id 和存到哪里,故无参。
100    /// 默认不做事(真机文件系统本来就持久)。
101    async fn save(&self) -> Result<(), SandboxError> {
102        Ok(())
103    }
104
105    /// 把自身当前状态复制给另一个会话 `to_session`。默认不支持。
106    async fn fork(&self, _to_session: &str) -> Result<(), SandboxError> {
107        Err(SandboxError::Unsupported)
108    }
109}