use serde_json::json;
use super::general_planner::compose_edit_request;
use super::planner::{fetch_arguments, plan_one, tool_for, AgenticPlan, Capability, Progress};
use super::tool_result;
use crate::protocol::ChatMessage;
pub(super) fn plan_web_fetch_step(
task: &str,
messages: &[ChatMessage],
tool_names: &[&str],
) -> Option<AgenticPlan> {
let url = crate::solver_handlers::agentic_fetch_url_for(task)?;
let tool = tool_for(tool_names, Capability::Fetch)?;
let progress = Progress::scan(messages);
if progress.done(Capability::Fetch) {
return Some(AgenticPlan::Final(tool_result::render(
"web_fetch",
progress.fetch_result().unwrap_or_default(),
task,
)));
}
Some(plan_one(tool, fetch_arguments(&url)))
}
pub(super) fn plan_web_search_step(
task: &str,
messages: &[ChatMessage],
tool_names: &[&str],
) -> Option<AgenticPlan> {
let query = crate::solver_handlers::web_search_query_for(task)?;
let Some(tool) = tool_for(tool_names, Capability::Search) else {
let discovery = tool_names
.iter()
.copied()
.find(|name| name.eq_ignore_ascii_case("tool_search"))?;
if tool_result_exists(messages, discovery) {
return None;
}
return Some(plan_one(
discovery,
json!({"query": "web search", "max_results": 5}).to_string(),
));
};
let progress = Progress::scan(messages);
if progress.done(Capability::Search) {
return Some(AgenticPlan::Final(tool_result::render(
"web_search",
progress.search_result().unwrap_or_default(),
task,
)));
}
Some(plan_one(tool, json!({ "query": query }).to_string()))
}
fn tool_result_exists(messages: &[ChatMessage], tool_name: &str) -> bool {
let current_turn = messages
.iter()
.rposition(|message| message.role.eq_ignore_ascii_case("user"))
.map_or(0, |index| index + 1);
messages
.iter()
.enumerate()
.skip(current_turn)
.any(|(index, message)| {
if !message.role.eq_ignore_ascii_case("tool") {
return false;
}
if message
.name
.as_deref()
.is_some_and(|name| name.eq_ignore_ascii_case(tool_name))
{
return true;
}
let Some(call_id) = message.tool_call_id.as_deref() else {
return false;
};
messages[..index]
.iter()
.flat_map(|prior| &prior.tool_calls)
.any(|call| {
call.id == call_id && call.function.name.eq_ignore_ascii_case(tool_name)
})
})
}
pub(super) fn plan_edit_step(
task: &str,
messages: &[ChatMessage],
tool_names: &[&str],
) -> Option<AgenticPlan> {
let (target, old, new) = compose_edit_request(task)?;
let tool = tool_for(tool_names, Capability::Edit)?;
let progress = Progress::scan(messages);
if progress.done(Capability::Edit) {
return tool_result::latest_turn_answer(messages, tool_names, task).map(AgenticPlan::Final);
}
if let Some(read_tool) =
tool_for(tool_names, Capability::Read).filter(|_| !progress.done(Capability::Read))
{
return Some(plan_one(read_tool, read_arguments(&target)));
}
Some(plan_one(tool, edit_arguments(&target, &old, &new)))
}
fn read_arguments(path: &str) -> String {
json!({
"path": path,
"filePath": path,
"file_path": path,
})
.to_string()
}
fn edit_arguments(path: &str, old: &str, new: &str) -> String {
json!({
"path": path,
"filePath": path,
"file_path": path,
"oldString": old,
"old_string": old,
"old_str": old,
"old": old,
"newString": new,
"new_string": new,
"new_str": new,
"new": new,
})
.to_string()
}