use std::collections::HashSet;
use std::sync::RwLock;
use swarm_engine_core::actions::ParamResolver;
use swarm_engine_core::agent::WorkResult;
use swarm_engine_core::environment::Environment;
use swarm_engine_core::types::{Action, WorkerId};
pub struct SearchEnvironment {
files: Vec<String>,
target_file: String,
target_content: String,
state: RwLock<SearchState>,
}
#[derive(Debug, Default)]
struct SearchState {
searched: bool,
read_files: HashSet<String>,
found_target: bool,
completed: Vec<WorkerId>,
}
impl SearchEnvironment {
pub fn new(
files: Vec<String>,
target_file: impl Into<String>,
target_content: impl Into<String>,
) -> Self {
Self {
files,
target_file: target_file.into(),
target_content: target_content.into(),
state: RwLock::new(SearchState::default()),
}
}
pub fn basic_scenario() -> Self {
let files = vec![
"src/config.rs".to_string(),
"src/utils.rs".to_string(),
"src/handler.rs".to_string(), "src/types.rs".to_string(),
"src/error.rs".to_string(),
];
Self::new(
files,
"src/handler.rs",
"fn handle_request() { /* implementation */ }",
)
}
pub fn medium_scenario() -> Self {
let files = vec![
"src/lib.rs".to_string(),
"src/config.rs".to_string(),
"src/utils.rs".to_string(),
"src/handler.rs".to_string(),
"src/types.rs".to_string(),
"src/error.rs".to_string(),
"src/api/mod.rs".to_string(),
"src/api/routes.rs".to_string(), "src/api/middleware.rs".to_string(),
"src/db/connection.rs".to_string(),
];
Self::new(
files,
"src/api/routes.rs",
"pub fn register_routes(app: &mut App) { /* routes */ }",
)
}
pub fn large_scenario() -> Self {
let files = vec![
"src/lib.rs".to_string(),
"src/main.rs".to_string(),
"src/config.rs".to_string(),
"src/utils.rs".to_string(),
"src/handler.rs".to_string(),
"src/types.rs".to_string(),
"src/error.rs".to_string(),
"src/api/mod.rs".to_string(),
"src/api/routes.rs".to_string(),
"src/api/middleware.rs".to_string(),
"src/db/mod.rs".to_string(),
"src/db/connection.rs".to_string(),
"src/db/queries.rs".to_string(),
"src/services/mod.rs".to_string(),
"src/services/auth.rs".to_string(), "src/services/user.rs".to_string(),
"src/services/payment.rs".to_string(),
"src/models/mod.rs".to_string(),
"src/models/user.rs".to_string(),
"src/models/order.rs".to_string(),
];
Self::new(
files,
"src/services/auth.rs",
"pub fn authenticate(token: &str) -> Result<User> { /* auth logic */ }",
)
}
pub fn custom_scenario(file_count: usize, target_index: usize, seed: u64) -> Self {
let file_count = file_count.clamp(2, 50);
let target_index = target_index.min(file_count - 1);
let mut rng_state = seed;
let mut next_rand = || {
rng_state = rng_state.wrapping_mul(6364136223846793005).wrapping_add(1);
rng_state
};
let prefixes = ["src", "lib", "core", "api", "services", "models", "utils"];
let suffixes = [
"mod", "types", "error", "handler", "config", "utils", "impl",
];
let files: Vec<String> = (0..file_count)
.map(|i| {
let prefix = prefixes[(next_rand() as usize) % prefixes.len()];
let suffix = suffixes[(next_rand() as usize) % suffixes.len()];
format!("{}/{}_{}.rs", prefix, suffix, i)
})
.collect();
let target_file = files[target_index].clone();
Self::new(
files,
target_file,
"// TARGET FILE CONTENT\nfn target_function() { /* found! */ }",
)
}
fn handle_search_files(&self, _worker_id: WorkerId, action: &Action) -> WorkResult {
let resolver = ParamResolver::new(action);
let query = resolver.get("query").unwrap_or("*.rs");
let mut state = self.state.write().unwrap();
state.searched = true;
let files_json: Vec<serde_json::Value> = self
.files
.iter()
.map(|f| serde_json::Value::String(f.clone()))
.collect();
let result = serde_json::json!({
"query": query,
"count": self.files.len(),
"files": files_json,
"message": "Use ReadFile to examine each file."
});
WorkResult::env_success_structured(result)
}
fn handle_read_file(&self, _worker_id: WorkerId, action: &Action) -> WorkResult {
let resolver = ParamResolver::new(action);
let file_path = match resolver.require("file") {
Ok(s) => s,
Err(e) => return WorkResult::env_failure(format!("ReadFile: {}", e)),
};
let mut state = self.state.write().unwrap();
if !self.files.contains(&file_path.to_string()) {
return WorkResult::env_failure(format!(
"File '{}' not found. Available files: {:?}",
file_path, self.files
));
}
state.read_files.insert(file_path.to_string());
if file_path == self.target_file {
state.found_target = true;
WorkResult::env_success(format!(
"=== {} ===\n{}\n\n[TARGET FOUND] This file contains the target content!",
file_path, self.target_content
))
} else {
WorkResult::env_failure(format!(
"=== {} ===\n// Empty or irrelevant content\n// Not the target file",
file_path
))
}
}
fn handle_analyze(&self, worker_id: WorkerId, action: &Action) -> WorkResult {
let resolver = ParamResolver::new(action);
let file_path = match resolver.require("file") {
Ok(s) => s,
Err(e) => return WorkResult::env_failure(format!("Analyze: {}", e)),
};
let mut state = self.state.write().unwrap();
if !state.found_target {
return WorkResult::env_failure("Cannot analyze without finding the target file first. Read files to find the target.");
}
if file_path != self.target_file {
return WorkResult::env_failure(format!(
"Cannot analyze '{}'. The target file is different.",
file_path
));
}
if !state.completed.contains(&worker_id) {
state.completed.push(worker_id);
}
WorkResult::done_success(format!(
"=== Analysis Complete ===\nFile: {}\nContent analyzed successfully!\n\nTask completed!",
file_path
))
}
}
impl Environment for SearchEnvironment {
fn step(&self, worker_id: WorkerId, action: &Action) -> WorkResult {
match action.name.to_lowercase().as_str() {
"searchfiles" | "search_files" | "search" | "list" | "listfiles" | "list_files" => {
self.handle_search_files(worker_id, action)
}
"readfile" | "read_file" | "read" | "cat" => self.handle_read_file(worker_id, action),
"analyze" | "process" | "complete" => self.handle_analyze(worker_id, action),
"continue" => WorkResult::env_success("Continuing..."),
_ => WorkResult::unsupported(&action.name),
}
}
fn reset(&self) {
let mut state = self.state.write().unwrap();
state.searched = false;
state.read_files.clear();
state.found_target = false;
state.completed.clear();
}
fn name(&self) -> &str {
"SearchEnvironment"
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
fn is_success(result: &WorkResult) -> bool {
match result {
WorkResult::Acted { action_result, .. } => action_result.success,
WorkResult::Done { success, .. } => *success,
_ => false,
}
}
fn is_done(result: &WorkResult) -> bool {
matches!(result, WorkResult::Done { .. })
}
fn action(name: &str, target: Option<&str>) -> Action {
Action {
name: name.into(),
params: swarm_engine_core::types::ActionParams {
target: target.map(|s| s.into()),
args: HashMap::new(),
data: vec![],
},
}
}
#[test]
fn test_search_files() {
let env = SearchEnvironment::basic_scenario();
let worker = WorkerId(0);
let result = env.step(worker, &action("SearchFiles", None));
assert!(is_success(&result));
assert!(!is_done(&result));
}
#[test]
fn test_read_file_without_search() {
let env = SearchEnvironment::basic_scenario();
let worker = WorkerId(0);
let result = env.step(worker, &action("ReadFile", Some("src/handler.rs")));
assert!(is_success(&result));
let env2 = SearchEnvironment::basic_scenario();
let result2 = env2.step(worker, &action("ReadFile", Some("src/config.rs")));
assert!(!is_success(&result2));
}
#[test]
fn test_read_target_file_success() {
let env = SearchEnvironment::basic_scenario();
let worker = WorkerId(0);
env.step(worker, &action("SearchFiles", None));
let result = env.step(worker, &action("ReadFile", Some("src/handler.rs")));
assert!(is_success(&result));
}
#[test]
fn test_read_wrong_file_failure() {
let env = SearchEnvironment::basic_scenario();
let worker = WorkerId(0);
env.step(worker, &action("SearchFiles", None));
let result = env.step(worker, &action("ReadFile", Some("src/config.rs")));
assert!(!is_success(&result)); }
#[test]
fn test_full_search_flow() {
let env = SearchEnvironment::basic_scenario();
let worker = WorkerId(0);
let result = env.step(worker, &action("SearchFiles", None));
assert!(is_success(&result));
assert!(!is_done(&result));
let result = env.step(worker, &action("ReadFile", Some("src/config.rs")));
assert!(!is_success(&result));
assert!(!is_done(&result));
let result = env.step(worker, &action("ReadFile", Some("src/handler.rs")));
assert!(is_success(&result));
assert!(!is_done(&result));
let result = env.step(worker, &action("Analyze", Some("src/handler.rs")));
assert!(is_success(&result));
assert!(is_done(&result));
}
#[test]
fn test_analyze_requires_found_target() {
let env = SearchEnvironment::basic_scenario();
let worker = WorkerId(0);
env.step(worker, &action("SearchFiles", None));
let result = env.step(worker, &action("Analyze", Some("src/handler.rs")));
assert!(!is_success(&result));
}
#[test]
fn test_custom_scenario() {
let env = SearchEnvironment::custom_scenario(10, 3, 42);
let worker = WorkerId(0);
let result = env.step(worker, &action("SearchFiles", None));
assert!(is_success(&result));
}
}