use super::AgentRunner;
use super::constants::ROLE_USER;
use crate::config::constants::tools;
use crate::core::agent::session::AgentSessionState;
use crate::core::agent::task::TaskOutcome;
use crate::llm::providers::gemini::wire::{Content, Part};
use serde_json::Value;
use std::path::Path;
impl AgentRunner {
pub(super) fn check_for_loop(&self, name: &str, args: &Value, session_state: &mut AgentSessionState) -> bool {
let (warning, hard_limit) = {
let mut detector = self.loop_detector.lock();
let warning = detector.record_call(name, args);
let hard_limit = warning.is_some() && detector.is_hard_limit_exceeded(name);
(warning, hard_limit)
};
let Some(warning) = warning else {
return false;
};
if !self.quiet {
tracing::warn!(?warning, "Loop detector warning");
}
if hard_limit {
session_state.warnings.push(warning.clone());
session_state.conversation.push(Content {
role: ROLE_USER.to_owned(),
parts: vec![Part::Text { text: warning.clone(), thought_signature: None }],
});
session_state.is_completed = true;
session_state.outcome = TaskOutcome::LoopDetected;
true
} else {
session_state.warnings.push(warning.clone());
session_state.conversation.push(Content {
role: ROLE_USER.to_owned(),
parts: vec![Part::Text { text: warning, thought_signature: None }],
});
false
}
}
pub(super) fn normalize_tool_args(&self, name: &str, args: Value, session_state: &mut AgentSessionState) -> Value {
let mut normalized = match args {
Value::Object(map) => map,
other => return other,
};
let workspace_path = self._workspace.to_string_lossy().into_owned();
let fallback_dir = session_state.last_dir_path.clone().unwrap_or_else(|| workspace_path.clone());
if name == tools::LIST_FILES {
normalized
.entry("path".to_string())
.or_insert_with(|| Value::String(fallback_dir));
}
if matches!(name, tools::READ_FILE | tools::WRITE_FILE | tools::EDIT_FILE | tools::CREATE_FILE)
&& !(name == tools::READ_FILE && normalized.contains_key("file_path"))
&& let Some(last_file) = session_state.last_file_path.clone()
{
normalized.entry("path".to_string()).or_insert_with(|| Value::String(last_file));
}
let normalized = Value::Object(normalized);
if let Some(transform) = &self.tool_arg_transform {
return transform(name, normalized);
}
normalized
}
pub(super) fn update_last_paths_from_args(&self, name: &str, args: &Value, session_state: &mut AgentSessionState) {
let remember_file = |state: &mut AgentSessionState, path: &str| {
state.last_file_path = Some(path.to_string());
if let Some(parent) = Path::new(path).parent() {
state.last_dir_path = Some(parent.to_string_lossy().into_owned());
}
};
if let Some(path) = args.get("file_path").and_then(|value| value.as_str()) {
remember_file(session_state, path);
return;
}
let Some(path) = args.get("path").and_then(|value| value.as_str()) else {
return;
};
if matches!(name, tools::READ_FILE | tools::WRITE_FILE | tools::EDIT_FILE | tools::CREATE_FILE) {
remember_file(session_state, path);
} else {
session_state.last_dir_path = Some(path.to_string());
}
}
}