tiny-agent 0.3.0

一个小而完整的 Rust LLM Agent 运行时:可中断、可恢复、可观测、可插拔的 agent loop / A small but complete LLM agent runtime in Rust — an interruptible, resumable, observable, pluggable agent loop.
Documentation
use crate::sandbox::Sandbox;
use crate::tools::{ToolCtx, ToolOutcome, merge_tool_result};
use schemars::JsonSchema;
use serde::Deserialize;
use serde_json::json;
use std::sync::Arc;
use std::time::{Duration, Instant};

/// check 的等待窗口:有新输出 / 任务结束就提前返回,否则最多等这么久(避免模型空转狂查)。
const WAIT_WINDOW: Duration = Duration::from_secs(2);
const POLL_INTERVAL: Duration = Duration::from_millis(100);

#[derive(Deserialize, JsonSchema)]
pub struct CheckArgs {
    /// 后台工具任务 id(形如 `task_7`)
    pub id: String,
    /// 可选等待秒数。传了之后 check 会先等待这么多秒,再读取新增输出与状态。
    pub wait_secs: Option<u64>,
}

pub async fn check(
    _sandbox: Arc<dyn Sandbox>,
    args: CheckArgs,
    ctx: ToolCtx,
) -> Result<ToolOutcome, String> {
    if let Some(wait_secs) = args.wait_secs {
        tokio::time::sleep(Duration::from_secs(wait_secs)).await;
        let snap = ctx.tasks().poll(&args.id).ok_or_else(|| {
            format!(
                "未找到后台工具任务: {}(可能已被 kill,或进程重启后丢失)",
                args.id
            )
        })?;
        return snapshot_to_outcome(snap);
    }

    let deadline = Instant::now() + WAIT_WINDOW;
    loop {
        let snap = ctx.tasks().poll(&args.id).ok_or_else(|| {
            format!(
                "未找到后台工具任务: {}(可能已被 kill,或进程重启后丢失)",
                args.id
            )
        })?;

        if !snap.new_output.is_empty() || snap.finished || Instant::now() >= deadline {
            return snapshot_to_outcome(snap);
        }
        tokio::time::sleep(POLL_INTERVAL).await;
    }
}

fn snapshot_to_outcome(snap: crate::tasks::TaskSnapshot) -> Result<ToolOutcome, String> {
    if let Some(err) = snap.error {
        return Err(err);
    }
    if snap.finished {
        return Ok(merge_tool_result(
            "completed",
            snap.new_output,
            snap.result.unwrap_or_else(|| json!({})),
        )
        .into());
    }

    Ok(json!({
        "status": "running",
        "tool_name": snap.tool_name,
        "output": snap.new_output,
    })
    .into())
}