use super::*;
fn parse_agent_types(description: &str) -> Vec<String> {
let mut out = Vec::new();
for line in description.lines() {
let Some(rest) = line.trim_start().strip_prefix("- ") else {
continue;
};
let Some((name, _)) = rest.split_once(':') else {
continue;
};
let name = name.trim();
if !name.is_empty()
&& name.len() <= 64
&& name
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_')
{
out.push(name.to_string());
}
}
out
}
fn infer_default_subagent_type(description: Option<&str>) -> String {
let candidates = description.map(parse_agent_types).unwrap_or_default();
candidates
.iter()
.find(|c| c.to_ascii_lowercase().contains("general"))
.or_else(|| candidates.first())
.cloned()
.unwrap_or_else(|| "general".to_string())
}
pub fn backfill_required_params(calls: &mut [ToolCall], tools: &[ToolDefinition]) {
for call in calls.iter_mut() {
let Some(tool_def) = tools.iter().find(|t| t.function.name == call.function.name) else {
continue;
};
let Some(ref params_schema) = tool_def.function.parameters else {
continue;
};
let required = params_schema
.get("required")
.and_then(|r| r.as_array())
.map(|a| a.iter().filter_map(|v| v.as_str()).collect::<Vec<_>>())
.unwrap_or_default();
let properties = params_schema.get("properties").and_then(|p| p.as_object());
let Ok(mut args) = serde_json::from_str::<serde_json::Map<String, serde_json::Value>>(
&call.function.arguments,
) else {
continue;
};
let mut changed = false;
if let Some(props) = properties {
for (key, value) in args.iter_mut() {
let expected_type = props.get(key).and_then(|p| resolve_schema_type(p));
if let (Some(expected), serde_json::Value::String(s)) = (expected_type, &value) {
let coerced = match expected {
"number" => s.parse::<f64>().ok().map(|n| {
serde_json::Value::Number(
serde_json::Number::from_f64(n)
.unwrap_or(serde_json::Number::from(0)),
)
}),
"integer" => s
.parse::<i64>()
.ok()
.map(|n| serde_json::Value::Number(n.into())),
"boolean" => match s.to_lowercase().as_str() {
"true" | "1" | "yes" => Some(serde_json::Value::Bool(true)),
"false" | "0" | "no" => Some(serde_json::Value::Bool(false)),
_ => None,
},
"object" | "array" => serde_json::from_str(s).ok(),
_ => None,
};
if let Some(new_val) = coerced {
*value = new_val;
changed = true;
}
}
}
}
if properties.is_some() {
let keys_to_fix: Vec<(String, String)> = args
.keys()
.map(|k| {
(
k.clone(),
normalize_param_name(tools, &call.function.name, k),
)
})
.filter(|(orig, mapped)| orig != mapped)
.collect();
for (wrong_key, right_key) in keys_to_fix {
if let Some(val) = args.remove(&wrong_key) {
args.entry(right_key).or_insert(val);
changed = true;
}
}
}
if properties.is_some() {
let salvageable: Vec<(String, String, String)> = args
.iter()
.filter_map(|(k, v)| {
let s = v.as_str()?;
salvage_echoed_param(tools, &call.function.name, k, s)
.map(|(real_key, real_val)| (k.clone(), real_key, real_val))
})
.collect();
for (echoed_key, real_key, real_val) in salvageable {
let target_empty = matches!(
args.get(&real_key),
None | Some(serde_json::Value::String(_))
) && args
.get(&real_key)
.and_then(|v| v.as_str())
.is_none_or(|s| s.trim().is_empty());
if target_empty {
args.remove(&echoed_key);
args.insert(real_key, serde_json::Value::String(real_val));
changed = true;
}
}
}
for key in &required {
if !args.contains_key(*key) {
let is_string = properties
.and_then(|p| p.get(*key))
.and_then(|v| v.get("type"))
.and_then(|t| t.as_str())
.is_none_or(|t| t == "string");
if is_string {
args.insert(key.to_string(), serde_json::Value::String(String::new()));
changed = true;
}
}
}
let func_name = call.function.name.clone();
for key in &required {
if let Some(serde_json::Value::String(val)) = args.get(*key) {
if !val.trim().is_empty() {
continue;
}
let auto_val = match *key {
"description" => {
if let Some(serde_json::Value::String(cmd)) = args.get("command") {
if cmd.len() > 50 {
let head: String = cmd.chars().take(47).collect();
format!("Run: {head}...")
} else {
format!("Run: {cmd}")
}
} else {
format!("{func_name} operation")
}
}
"filePath" | "file_path" => {
continue;
}
"oldString" | "old_string" => {
continue;
}
"subagent_type" | "subagentType" => {
infer_default_subagent_type(tool_def.function.description.as_deref())
}
_ => continue,
};
args.insert(key.to_string(), serde_json::Value::String(auto_val));
changed = true;
}
}
if changed && let Ok(new_args) = serde_json::to_string(&serde_json::Value::Object(args)) {
call.function.arguments = new_args;
}
}
}