use super::{MigrateError, MigrationResult};
pub fn migrate_agent_retry_to_tools_retry(toml_src: &str) -> Result<MigrationResult, MigrateError> {
let mut doc = toml_src.parse::<toml_edit::DocumentMut>()?;
let max_retries = doc
.get("agent")
.and_then(toml_edit::Item::as_table)
.and_then(|t| t.get("max_tool_retries"))
.and_then(toml_edit::Item::as_value)
.and_then(toml_edit::Value::as_integer)
.map(i64::cast_unsigned);
let budget_secs = doc
.get("agent")
.and_then(toml_edit::Item::as_table)
.and_then(|t| t.get("max_retry_duration_secs"))
.and_then(toml_edit::Item::as_value)
.and_then(toml_edit::Value::as_integer)
.map(i64::cast_unsigned);
if max_retries.is_none() && budget_secs.is_none() {
return Ok(MigrationResult {
output: toml_src.to_owned(),
changed_count: 0,
sections_changed: Vec::new(),
});
}
if !doc.contains_key("tools") {
doc.insert("tools", toml_edit::Item::Table(toml_edit::Table::new()));
}
let tools_table = doc
.get_mut("tools")
.and_then(toml_edit::Item::as_table_mut)
.ok_or(MigrateError::InvalidStructure("[tools] is not a table"))?;
if !tools_table.contains_key("retry") {
tools_table.insert("retry", toml_edit::Item::Table(toml_edit::Table::new()));
}
let retry_table = tools_table
.get_mut("retry")
.and_then(toml_edit::Item::as_table_mut)
.ok_or(MigrateError::InvalidStructure(
"[tools.retry] is not a table",
))?;
let mut changed_count = 0usize;
if let Some(retries) = max_retries
&& !retry_table.contains_key("max_attempts")
{
retry_table.insert(
"max_attempts",
toml_edit::value(i64::try_from(retries).unwrap_or(2)),
);
changed_count += 1;
}
if let Some(secs) = budget_secs
&& !retry_table.contains_key("budget_secs")
{
retry_table.insert(
"budget_secs",
toml_edit::value(i64::try_from(secs).unwrap_or(30)),
);
changed_count += 1;
}
if changed_count > 0 {
eprintln!(
"Migration: [agent].max_tool_retries / max_retry_duration_secs migrated to \
[tools.retry].max_attempts / budget_secs. Old fields preserved for compatibility."
);
}
Ok(MigrationResult {
output: doc.to_string(),
changed_count,
sections_changed: if changed_count > 0 {
vec!["tools.retry".to_owned()]
} else {
Vec::new()
},
})
}
pub fn migrate_agent_budget_hint(toml_src: &str) -> Result<MigrationResult, MigrateError> {
if toml_src.contains("budget_hint_enabled") {
return Ok(MigrationResult {
output: toml_src.to_owned(),
changed_count: 0,
sections_changed: Vec::new(),
});
}
let doc = toml_src.parse::<toml_edit::DocumentMut>()?;
if !doc.contains_key("agent") {
return Ok(MigrationResult {
output: toml_src.to_owned(),
changed_count: 0,
sections_changed: Vec::new(),
});
}
let comment = "\n# Inject <budget> XML into the system prompt so the LLM can self-regulate (#2267).\n\
# budget_hint_enabled = true\n";
let raw = doc.to_string();
let output = format!("{raw}{comment}");
Ok(MigrationResult {
output,
changed_count: 1,
sections_changed: vec!["agent.budget_hint_enabled".to_owned()],
})
}
pub fn migrate_quality_config(toml_src: &str) -> Result<MigrationResult, MigrateError> {
if toml_src
.lines()
.any(|l| l.trim() == "[quality]" || l.trim() == "# [quality]")
{
return Ok(MigrationResult {
output: toml_src.to_owned(),
changed_count: 0,
sections_changed: Vec::new(),
});
}
let comment = "\n# [quality] — MARCH Proposer+Checker self-check pipeline (#3226, #3228).\n\
# [quality]\n\
# self_check = false # enable post-response self-check\n\
# trigger = \"has_retrieval\" # has_retrieval | always | manual\n\
# latency_budget_ms = 4000 # hard ceiling for the whole pipeline\n\
# proposer_provider = \"\" # optional: provider name from [[llm.providers]]\n\
# checker_provider = \"\" # optional: provider name from [[llm.providers]]\n\
# min_evidence = 0.6 # 0.0..1.0; below → flag assertion\n\
# async_run = false # true = fire-and-forget (non-blocking)\n\
# per_call_timeout_ms = 2000 # per-LLM-call timeout\n\
# max_assertions = 12 # maximum assertions extracted from one response\n\
# max_response_chars = 8000 # skip pipeline when response exceeds this\n\
# cache_disabled_for_checker = true # suppress prompt-cache on Checker provider\n\
# flag_marker = \"[verify]\" # marker appended when assertions are flagged\n";
let output = format!("{toml_src}{comment}");
Ok(MigrationResult {
output,
changed_count: 1,
sections_changed: vec!["quality".to_owned()],
})
}
pub fn migrate_tools_compression_config(toml_src: &str) -> Result<MigrationResult, MigrateError> {
if toml_src.contains("tools.compression")
|| toml_src.contains("[tools]\n") && toml_src.contains("compression")
{
return Ok(MigrationResult {
output: toml_src.to_owned(),
changed_count: 0,
sections_changed: Vec::new(),
});
}
let comment = "\n# TACO self-evolving tool output compression (#3306).\n\
# [tools.compression]\n\
# enabled = false\n\
# min_lines_to_compress = 10\n\
# evolution_provider = \"\"\n\
# evolution_min_interval_secs = 3600\n\
# max_rules = 200\n";
Ok(MigrationResult {
output: format!("{toml_src}{comment}"),
changed_count: 1,
sections_changed: vec!["tools.compression".to_owned()],
})
}
pub fn migrate_policy_provider_and_utility_window(
toml_src: &str,
) -> Result<MigrationResult, MigrateError> {
let already_policy = toml_src.contains("policy_provider");
let already_window = toml_src.contains("utility_window");
if already_policy && already_window {
return Ok(MigrationResult {
output: toml_src.to_owned(),
changed_count: 0,
sections_changed: Vec::new(),
});
}
let mut output = toml_src.to_owned();
let mut changed_count = 0usize;
let mut sections_changed: Vec<String> = Vec::new();
if !already_policy {
output.push_str(
"\n# LLM provider name for policy checks. Empty = disabled (rules-only). (#5068)\n\
# [tools.policy]\n\
# policy_provider = \"\"\n",
);
changed_count += 1;
sections_changed.push("tools.policy".to_owned());
}
if !already_window {
output.push_str(
"\n# Consecutive low-utility tool calls before the loop hard-stops. 0 = disabled. (#5068)\n\
# [tools.utility]\n\
# utility_window = 0\n",
);
changed_count += 1;
sections_changed.push("tools.utility".to_owned());
}
Ok(MigrationResult {
output,
changed_count,
sections_changed,
})
}
pub fn migrate_utility_high_gain_tools(toml_src: &str) -> Result<MigrationResult, MigrateError> {
if toml_src.contains("high_gain_tools") {
return Ok(MigrationResult {
output: toml_src.to_owned(),
changed_count: 0,
sections_changed: Vec::new(),
});
}
let comment = "\n# Tool ids that always receive the 0.75 \"direct action\" gain tier, bypassing\n\
# the Retrieve detour on their first call. Useful for MCP-registered tools, whose\n\
# ids (\"{server_id}_{name}\") never match the built-in default_gain table (#5659).\n\
# [tools.utility]\n\
# high_gain_tools = []\n";
Ok(MigrationResult {
output: format!("{toml_src}{comment}"),
changed_count: 1,
sections_changed: vec!["tools.utility.high_gain_tools".to_owned()],
})
}
pub fn migrate_shell_checkpoints_config(toml_src: &str) -> Result<MigrationResult, MigrateError> {
if toml_src.contains("checkpoints_enabled") || toml_src.contains("max_checkpoints") {
return Ok(MigrationResult {
output: toml_src.to_owned(),
changed_count: 0,
sections_changed: Vec::new(),
});
}
let comment = "\n# Session-scoped /undo and /redo checkpoint history (#4990).\n\
# Checkpoints are in-memory only and lost on agent restart.\n\
# [tools.shell]\n\
# checkpoints_enabled = false\n\
# max_checkpoints = 20\n";
Ok(MigrationResult {
output: format!("{toml_src}{comment}"),
changed_count: 1,
sections_changed: vec!["tools.shell".to_owned()],
})
}