codetether_agent/tool/
rlm.rs1use super::{Tool, ToolResult};
7use crate::rlm::{RlmChunker, RlmConfig, RlmRouter};
8use crate::rlm::router::AutoProcessContext;
9use crate::provider::Provider;
10use anyhow::Result;
11use async_trait::async_trait;
12use serde_json::{Value, json};
13use std::sync::Arc;
14
15pub struct RlmTool {
18 provider: Arc<dyn Provider>,
19 model: String,
20}
21
22impl RlmTool {
23 pub fn new(provider: Arc<dyn Provider>, model: String) -> Self {
24 Self { provider, model }
25 }
26}
27
28#[async_trait]
29impl Tool for RlmTool {
30 fn id(&self) -> &str {
31 "rlm"
32 }
33
34 fn name(&self) -> &str {
35 "RLM"
36 }
37
38 fn description(&self) -> &str {
39 "Recursive Language Model for processing large codebases. Use this when you need to analyze files or content that exceeds the context window. RLM chunks the content, processes each chunk, and synthesizes results. Actions: 'analyze' (analyze large content), 'summarize' (summarize large files), 'search' (semantic search across large codebase)."
40 }
41
42 fn parameters(&self) -> Value {
43 json!({
44 "type": "object",
45 "properties": {
46 "action": {
47 "type": "string",
48 "description": "Action: 'analyze' (deep analysis), 'summarize' (generate summary), 'search' (semantic search)",
49 "enum": ["analyze", "summarize", "search"]
50 },
51 "query": {
52 "type": "string",
53 "description": "The question or query to answer (for analyze/search)"
54 },
55 "paths": {
56 "type": "array",
57 "items": {"type": "string"},
58 "description": "File or directory paths to process"
59 },
60 "content": {
61 "type": "string",
62 "description": "Direct content to analyze (alternative to paths)"
63 },
64 "max_depth": {
65 "type": "integer",
66 "description": "Maximum recursion depth (default: 3)",
67 "default": 3
68 }
69 },
70 "required": ["action"]
71 })
72 }
73
74 async fn execute(&self, args: Value) -> Result<ToolResult> {
75 let action = args["action"]
76 .as_str()
77 .ok_or_else(|| anyhow::anyhow!("action is required"))?;
78
79 let query = args["query"].as_str().unwrap_or("");
80 let paths: Vec<&str> = args["paths"]
81 .as_array()
82 .map(|arr| arr.iter().filter_map(|v| v.as_str()).collect())
83 .unwrap_or_default();
84 let content = args["content"].as_str();
85
86 match action {
87 "analyze" | "summarize" | "search" => {
88 if action != "summarize" && query.is_empty() {
89 return Ok(ToolResult::error(format!(
90 "query is required for '{}' action", action
91 )));
92 }
93
94 let all_content = if let Some(c) = content {
96 c.to_string()
97 } else if !paths.is_empty() {
98 let mut collected = String::new();
99 for path in &paths {
100 match tokio::fs::read_to_string(path).await {
101 Ok(c) => {
102 collected.push_str(&format!("=== {} ===\n{}\n\n", path, c));
103 }
104 Err(e) => {
105 collected.push_str(&format!("=== {} (error: {}) ===\n\n", path, e));
106 }
107 }
108 }
109 collected
110 } else {
111 return Ok(ToolResult::error("Either 'paths' or 'content' is required"));
112 };
113
114 let input_tokens = RlmChunker::estimate_tokens(&all_content);
115 let effective_query = if query.is_empty() {
116 format!("Summarize the content from: {:?}", paths)
117 } else {
118 query.to_string()
119 };
120
121 let auto_ctx = AutoProcessContext {
123 tool_id: action,
124 tool_args: json!({ "query": effective_query, "paths": paths }),
125 session_id: "rlm-tool",
126 abort: None,
127 on_progress: None,
128 provider: Arc::clone(&self.provider),
129 model: self.model.clone(),
130 };
131 let config = RlmConfig::default();
132
133 match RlmRouter::auto_process(&all_content, auto_ctx, &config).await {
134 Ok(result) => {
135 let output = format!(
136 "RLM {} complete ({} → {} tokens, {} iterations)\n\n{}",
137 action,
138 result.stats.input_tokens,
139 result.stats.output_tokens,
140 result.stats.iterations,
141 result.processed
142 );
143 Ok(ToolResult::success(output))
144 }
145 Err(e) => {
146 tracing::warn!(error = %e, "RLM auto_process failed, falling back to truncation");
148 let (truncated, _, _) = RlmRouter::smart_truncate(
149 &all_content, action, &json!({}), input_tokens.min(8000),
150 );
151 Ok(ToolResult::success(format!(
152 "RLM {} (fallback mode - auto_process failed: {})\n\n{}",
153 action, e, truncated
154 )))
155 }
156 }
157 }
158 _ => Ok(ToolResult::error(format!(
159 "Unknown action: {}. Use 'analyze', 'summarize', or 'search'.",
160 action
161 ))),
162 }
163 }
164}