agent-base 0.5.0

A lightweight Agent Runtime Kernel for building AI agents in Rust
Documentation
use async_trait::async_trait;
use serde_json::{Value, json};

use crate::tool::{Content, Tool, ToolContext};
use crate::types::AgentResult;

/// Pure orchestration signal tool with zero domain dependency.
///
/// When the agent encounters an error in the previous tool execution and has
/// found a solution or retry strategy, or when it needs to execute multiple
/// long tasks in sequence, it can call this tool to request the system to
/// automatically continue to the next turn without waiting for user reply.
#[derive(Clone, Debug, Default)]
pub struct AutoContinueTool;

impl AutoContinueTool {
    pub fn new() -> Self {
        Self
    }
}

#[async_trait]
impl Tool for AutoContinueTool {
    fn name(&self) -> &'static str {
        "request_auto_continue"
    }

    fn description(&self) -> &'static str {
        "When you encounter an error in the previous tool execution and have found a solution or retry strategy, or when you need to execute multiple long tasks in sequence, call this tool to request the system to automatically continue to the next turn instead of stopping and waiting for user reply. This tool is only for making the request; you still need to describe your solution in the prompt."
    }

    fn schema(&self) -> Value {
        json!({
            "type": "object",
            "properties": {
                "reason": {
                    "type": "string",
                    "description": "A brief reason for requesting auto-continue, e.g. 'network error, trying alternative source'"
                }
            },
            "required": ["reason"]
        })
    }

    fn metadata(&self) -> crate::tool::ToolMetadata {
        crate::tool::ToolMetadata {
            name: self.name().to_string(),
            description:
                "Request automatic continuation to the next turn without waiting for user input."
                    .to_string(),
            origin: "agent-base".to_string(),
            version: env!("CARGO_PKG_VERSION").to_string(),
            requirements: vec![],
        }
    }

    async fn call(&self, args: &Value, _ctx: &ToolContext) -> AgentResult<Vec<Content>> {
        let reason = args
            .get("reason")
            .and_then(Value::as_str)
            .unwrap_or("no reason provided");

        Ok(vec![Content::text(format!(
            "Auto-continue request received. Reason: {}",
            reason
        ))])
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::tool::content_text;
    use serde_json::json;

    #[test]
    fn name_is_stable() {
        assert_eq!(AutoContinueTool::new().name(), "request_auto_continue");
    }

    #[test]
    fn schema_requires_reason() {
        let schema = AutoContinueTool::new().schema();
        assert_eq!(schema["type"], "object");
        assert!(schema["properties"]["reason"].is_object());
        assert_eq!(schema["required"], json!(["reason"]));
    }

    #[test]
    fn metadata_origin_is_agent_base() {
        let m = AutoContinueTool::new().metadata();
        assert_eq!(m.origin, "agent-base");
        assert_eq!(m.name, "request_auto_continue");
    }

    #[tokio::test]
    async fn call_with_reason_echoes_reason() {
        let tool = AutoContinueTool::new();
        let ctx = ToolContext::for_test();
        let out = tool
            .call(&json!({"reason": "retry after network error"}), &ctx)
            .await
            .unwrap();
        assert!(content_text(&out).contains("retry after network error"));
    }

    #[tokio::test]
    async fn call_without_reason_uses_default() {
        let tool = AutoContinueTool::new();
        let ctx = ToolContext::for_test();
        let out = tool.call(&json!({}), &ctx).await.unwrap();
        assert!(content_text(&out).contains("no reason provided"));
    }
}