ai-agent 0.88.0

Idiomatic agent sdk inspired by the claude code source leak
Documentation
// Source: ~/claudecode/openclaudecode/src/tools/AgentTool/built-in/exploreAgent.ts
#![allow(dead_code)]
use std::sync::Arc;

use super::super::AgentDefinition;

const BASH_TOOL_NAME: &str = "Bash";
const EXIT_PLAN_MODE_TOOL_NAME: &str = "ExitPlanMode";
const FILE_EDIT_TOOL_NAME: &str = "FileEdit";
const FILE_READ_TOOL_NAME: &str = "Read";
const FILE_WRITE_TOOL_NAME: &str = "Write";
const GLOB_TOOL_NAME: &str = "Glob";
const GREP_TOOL_NAME: &str = "Grep";
const NOTEBOOK_EDIT_TOOL_NAME: &str = "NotebookEdit";
const AGENT_TOOL_NAME: &str = "Agent";

/// Check if embedded search tools are available.
pub fn has_embedded_search_tools() -> bool {
    std::env::var("AI_CODE_EMBEDDED_SEARCH_TOOLS")
        .map(|v| v == "1" || v == "true" || v == "yes")
        .unwrap_or(false)
}

pub fn get_explore_system_prompt() -> String {
    let embedded = has_embedded_search_tools();
    let glob_guidance = if embedded {
        format!(
            "- Use `find` via {} for broad file pattern matching",
            BASH_TOOL_NAME
        )
    } else {
        format!("- Use {} for broad file pattern matching", GLOB_TOOL_NAME)
    };
    let grep_guidance = if embedded {
        format!(
            "- Use `grep` via {} for searching file contents with regex",
            BASH_TOOL_NAME
        )
    } else {
        format!(
            "- Use {} for searching file contents with regex",
            GREP_TOOL_NAME
        )
    };

    let embedded_note = if embedded { ", grep" } else { "" };

    format!(
        r#"You are a file search specialist for Claude Code, Anthropic's official CLI for Claude. You excel at thoroughly navigating and exploring codebases.

=== CRITICAL: READ-ONLY MODE - NO FILE MODIFICATIONS ===
This is a READ-ONLY exploration task. You are STRICTLY PROHIBITED from:
- Creating new files (no Write, touch, or file creation of any kind)
- Modifying existing files (no Edit operations)
- Deleting files (no rm or deletion)
- Moving or copying files (no mv or cp)
- Creating temporary files anywhere, including /tmp
- Using redirect operators (>, >>, |) or heredocs to write to files
- Running ANY commands that change system state

Your role is EXCLUSIVELY to search and analyze existing code. You do NOT have access to file editing tools - attempting to edit files will fail.

Your strengths:
- Rapidly finding files using glob patterns
- Searching code and text with powerful regex patterns
- Reading and analyzing file contents

Guidelines:
{glob_guidance}
{grep_guidance}
- Use {file_read} when you know the specific file path you need to read
- Use {bash} ONLY for read-only operations (ls, git status, git log, git diff, find{embedded_note}, cat, head, tail)
- NEVER use {bash} for: mkdir, touch, rm, cp, mv, git add, git commit, npm install, pip install, or any file creation/modification
- Adapt your search approach based on the thoroughness level specified by the caller
- Communicate your final report directly as a regular message - do NOT attempt to create files

NOTE: You are meant to be a fast agent that returns output as quickly as possible. In order to achieve this you must:
- Make efficient use of the tools that you have at your disposal: be smart about how you search for files and implementations
- Wherever possible you should try to spawn multiple parallel tool calls for grepping and reading files

Complete the user's search request efficiently and report your findings clearly."#,
        file_read = FILE_READ_TOOL_NAME,
        bash = BASH_TOOL_NAME,
        embedded_note = embedded_note,
    )
}

pub const EXPLORE_AGENT_MIN_QUERIES: usize = 3;

const EXPLORE_WHEN_TO_USE: &str = "Fast agent specialized for exploring codebases. Use this when you need to quickly find files by patterns (eg. \"src/components/**/*.tsx\"), search code for keywords (eg. \"API endpoints\"), or answer questions about the codebase (eg. \"how do API endpoints work?\"). When calling this agent, specify the desired thoroughness level: \"quick\" for basic searches, \"medium\" for moderate exploration, or \"very thorough\" for comprehensive analysis across multiple locations and naming conventions.";

pub fn explore_agent() -> AgentDefinition {
    let user_type = std::env::var("AI_CODE_USER_TYPE").unwrap_or_default();

    AgentDefinition {
        agent_type: "Explore".to_string(),
        when_to_use: EXPLORE_WHEN_TO_USE.to_string(),
        disallowed_tools: vec![
            AGENT_TOOL_NAME.to_string(),
            EXIT_PLAN_MODE_TOOL_NAME.to_string(),
            FILE_EDIT_TOOL_NAME.to_string(),
            FILE_WRITE_TOOL_NAME.to_string(),
            NOTEBOOK_EDIT_TOOL_NAME.to_string(),
        ],
        tools: vec!["*".to_string()],
        source: "built-in".to_string(),
        base_dir: "built-in".to_string(),
        // Ants get inherit to use the main agent's model; external users get haiku for speed
        model: if user_type == "ant" {
            Some("inherit".to_string())
        } else {
            Some("haiku".to_string())
        },
        max_turns: None,
        permission_mode: None,
        effort: None,
        color: None,
        mcp_servers: vec![],
        hooks: None,
        skills: vec![],
        background: false,
        initial_prompt: None,
        memory: None,
        isolation: None,
        required_mcp_servers: vec![],
        omit_claude_md: true,
        critical_system_reminder_experimental: None,
        get_system_prompt: Arc::new(get_explore_system_prompt),
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_explore_agent_built_in() {
        let agent = explore_agent();
        assert_eq!(agent.agent_type, "Explore");
        assert_eq!(agent.source, "built-in");
        assert!(agent.omit_claude_md);
    }

    #[test]
    fn test_explore_agent_disallowed_tools() {
        let agent = explore_agent();
        assert!(agent.disallowed_tools.contains(&"Write".to_string()));
        assert!(agent.disallowed_tools.contains(&"FileEdit".to_string()));
    }

    #[test]
    fn test_explore_system_prompt_contains_readonly() {
        let prompt = get_explore_system_prompt();
        assert!(prompt.contains("READ-ONLY"));
        assert!(prompt.contains("STRICTLY PROHIBITED"));
    }
}