codetether_agent/tool/
rlm.rs1use super::{Tool, ToolResult};
7use crate::provider::Provider;
8use crate::rlm::router::AutoProcessContext;
9use crate::rlm::{RlmChunker, RlmConfig, RlmRouter};
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",
91 action
92 )));
93 }
94
95 let all_content = if let Some(c) = content {
97 c.to_string()
98 } else if !paths.is_empty() {
99 let mut collected = String::new();
100 for path in &paths {
101 match tokio::fs::read_to_string(path).await {
102 Ok(c) => {
103 collected.push_str(&format!("=== {} ===\n{}\n\n", path, c));
104 }
105 Err(e) => {
106 collected.push_str(&format!("=== {} (error: {}) ===\n\n", path, e));
107 }
108 }
109 }
110 collected
111 } else {
112 return Ok(ToolResult::error("Either 'paths' or 'content' is required"));
113 };
114
115 let input_tokens = RlmChunker::estimate_tokens(&all_content);
116 let effective_query = if query.is_empty() {
117 format!("Summarize the content from: {:?}", paths)
118 } else {
119 query.to_string()
120 };
121
122 let auto_ctx = AutoProcessContext {
124 tool_id: action,
125 tool_args: json!({ "query": effective_query, "paths": paths }),
126 session_id: "rlm-tool",
127 abort: None,
128 on_progress: None,
129 provider: Arc::clone(&self.provider),
130 model: self.model.clone(),
131 };
132 let config = RlmConfig::default();
133
134 match RlmRouter::auto_process(&all_content, auto_ctx, &config).await {
135 Ok(result) => {
136 let output = format!(
137 "RLM {} complete ({} → {} tokens, {} iterations)\n\n{}",
138 action,
139 result.stats.input_tokens,
140 result.stats.output_tokens,
141 result.stats.iterations,
142 result.processed
143 );
144 Ok(ToolResult::success(output))
145 }
146 Err(e) => {
147 tracing::warn!(error = %e, "RLM auto_process failed, falling back to truncation");
149 let (truncated, _, _) = RlmRouter::smart_truncate(
150 &all_content,
151 action,
152 &json!({}),
153 input_tokens.min(8000),
154 );
155 Ok(ToolResult::success(format!(
156 "RLM {} (fallback mode - auto_process failed: {})\n\n{}",
157 action, e, truncated
158 )))
159 }
160 }
161 }
162 _ => Ok(ToolResult::error(format!(
163 "Unknown action: {}. Use 'analyze', 'summarize', or 'search'.",
164 action
165 ))),
166 }
167 }
168}