#![allow(unused_imports, dead_code)]
use super::*;
pub(super) fn parse_complete_call(
text: &str,
idx: u32,
promote_bare_names: bool,
) -> Option<ToolCall> {
let body = text.trim();
if is_tool_name_component(body) {
if promote_bare_names {
return parse_poolside_v1_call(body);
}
tracing::warn!(
"tool_parser: dropped bare-identifier <tool_call> body {body:?} \
(parser does not promote bare names; pre-batch4 behaviour)"
);
return None;
}
parse_one_call(body, idx)
}
pub(super) fn parse_one_call(text: &str, idx: u32) -> Option<ToolCall> {
if text.contains("<arg_key>")
&& let Some(tc) = parse_poolside_v1_call(text)
{
return Some(tc);
}
if text.starts_with("call:") || text.starts_with("_call:") {
return parse_gemma4_native_call(text);
}
if let Ok(v) = serde_json::from_str::<serde_json::Value>(text) {
let name = normalize_tool_name(v.get("name")?.as_str()?);
let args = v
.get("arguments")
.map(|a| {
if let Some(s) = a.as_str() {
s.to_string()
} else {
serde_json::to_string(a).unwrap_or_else(|_| "{}".into())
}
})
.unwrap_or_else(|| "{}".into());
return Some(ToolCall {
id: next_tool_call_id(),
call_type: "function".into(),
function: FunctionCall {
name,
arguments: args,
},
});
}
if text.contains("\"name\"") && text.contains("\"arguments\"") {
let name = extract_json_string(text, "name");
if let Some(name) = name {
let name = normalize_tool_name(&name);
let args = if let Some(args_start) = text.find("\"arguments\"") {
let after = &text[args_start + "\"arguments\"".len()..];
let colon = after.find(':').map(|p| p + 1).unwrap_or(0);
let args_text = after[colon..].trim();
if let Ok(v) = serde_json::from_str::<serde_json::Value>(args_text) {
serde_json::to_string(&v).unwrap_or_else(|_| "{}".into())
} else {
let mut best = "{}".to_string();
for (i, ch) in args_text.char_indices().rev() {
if (ch == '}' || ch == ']')
&& serde_json::from_str::<serde_json::Value>(&args_text[..=i]).is_ok()
{
best = args_text[..=i].to_string();
break;
}
}
best
}
} else {
"{}".to_string()
};
return Some(ToolCall {
id: next_tool_call_id(),
call_type: "function".into(),
function: FunctionCall {
name,
arguments: args,
},
});
}
}
if let Some(tc) = parse_minimax_xml_call(text, idx) {
return Some(tc);
}
if let Some(tc) = parse_qwen3_coder_call(text, idx) {
return Some(tc);
}
parse_tag_style_call(text, idx)
}
pub(super) fn parse_minimax_xml_call(text: &str, idx: u32) -> Option<ToolCall> {
let _ = idx;
let invoke_start = text
.find("<invoke name=\"")
.or_else(|| text.find("<invoke name='"))?;
let after = &text[invoke_start + "<invoke name=".len()..];
let quote = after.as_bytes().first().copied()?;
if quote != b'"' && quote != b'\'' {
return None;
}
let name_start = 1;
let name_end = after[name_start..]
.find(quote as char)
.map(|p| p + name_start)?;
let func_name = normalize_tool_name(&after[name_start..name_end]);
if func_name.is_empty() {
return None;
}
let mut rest = &after[name_end + 1..];
if let Some(gt) = rest.find('>') {
rest = &rest[gt + 1..];
} else {
return None;
}
let mut args = serde_json::Map::new();
while let Some(p) = rest.find("<parameter name=") {
rest = &rest[p + "<parameter name=".len()..];
let q = rest.as_bytes().first().copied()?;
if q != b'"' && q != b'\'' {
break;
}
let key_start = 1;
let key_end = rest[key_start..].find(q as char).map(|p| p + key_start)?;
let param_name = rest[key_start..key_end].trim().to_string();
rest = &rest[key_end + 1..];
let gt = rest.find('>')?;
rest = &rest[gt + 1..];
let proper = rest.find("</parameter>");
let next_param = rest.find("<parameter=");
let func_close = rest.find("</function>");
let mut val_end = rest.len();
let mut consumed_close = false;
if let Some(p) = proper {
val_end = p;
consumed_close = true;
}
for cand in [next_param, func_close].into_iter().flatten() {
if cand < val_end {
val_end = cand;
consumed_close = false;
}
}
let raw_value = rest[..val_end].trim();
rest = if consumed_close {
&rest[val_end + "</parameter>".len()..]
} else if val_end < rest.len() {
&rest[val_end..]
} else {
""
};
args.insert(param_name, serde_json::Value::String(raw_value.to_string()));
}
const PATH_KEYS: &[&str] = &["file_path", "filePath", "path"];
let is_write_tool = matches!(
func_name.as_str(),
"Write" | "write" | "Edit" | "edit" | "MultiEdit" | "multiEdit" | "multi_edit",
);
if is_write_tool {
for key in PATH_KEYS {
if let Some(serde_json::Value::String(v)) = args.get(*key)
&& v.trim().is_empty()
{
tracing::warn!(
tool = %func_name,
key = key,
"F80b: dropping minimax_xml call with empty required path; \
model self-truncation"
);
return None;
}
}
}
Some(ToolCall {
id: next_tool_call_id(),
call_type: "function".into(),
function: FunctionCall {
name: func_name,
arguments: serde_json::to_string(&serde_json::Value::Object(args))
.unwrap_or_else(|_| "{}".into()),
},
})
}
pub(crate) fn parse_minimax_xml_calls_all(text: &str) -> Vec<ToolCall> {
let mut out = Vec::new();
let mut rest = text;
let mut idx: u32 = 0;
while let Some(start) = rest.find("<invoke name=") {
let chunk = &rest[start..];
let end = match chunk.find("</invoke>") {
Some(e) => e + "</invoke>".len(),
None => chunk.len(),
};
if let Some(tc) = parse_minimax_xml_call(&chunk[..end], idx) {
out.push(tc);
idx += 1;
}
rest = &chunk[end..];
}
out
}
fn extract_json_string(text: &str, key: &str) -> Option<String> {
let pattern = format!("\"{}\"", key);
let key_pos = text.find(&pattern)?;
let after_key = &text[key_pos + pattern.len()..];
let colon = after_key.find(':')?;
let after_colon = after_key[colon + 1..].trim_start();
if !after_colon.starts_with('"') {
return None;
}
let val_start = 1;
let mut i = val_start;
let bytes = after_colon.as_bytes();
while i < bytes.len() {
if bytes[i] == b'\\' {
i += 2;
} else if bytes[i] == b'"' {
return Some(after_colon[val_start..i].to_string());
} else {
i += 1;
}
}
None
}