#![allow(unused_imports, dead_code)]
use super::*;
pub(super) fn parse_mistral_native_call(segment: &str) -> Option<ToolCall> {
let segment = segment.trim_start();
let (name, json_slice) = if let Some(args_pos) = segment.find(MISTRAL_ARGS_TAG) {
let name = normalize_tool_name(&segment[..args_pos]);
let after_args = &segment[args_pos + MISTRAL_ARGS_TAG.len()..];
let raw_args = after_args.trim();
let json_start = raw_args.find('{').unwrap_or(0);
(name, &raw_args[json_start..])
} else if let Some(brace_pos) = segment.find('{') {
let name = normalize_tool_name(&segment[..brace_pos]);
(name, &segment[brace_pos..])
} else {
return None;
};
if !is_normalized_tool_name(&name) {
return None;
}
if !name.chars().all(is_tool_name_or_namespace_char) {
return None;
}
let args = if let Ok(v) = serde_json::from_str::<serde_json::Value>(json_slice) {
serde_json::to_string(&v).unwrap_or_else(|_| "{}".into())
} else if let Some(valid) = largest_balanced_json_prefix(json_slice) {
valid.to_string()
} else {
"{}".to_string()
};
Some(ToolCall {
id: next_tool_call_id(),
call_type: "function".into(),
function: FunctionCall {
name,
arguments: args,
},
})
}
pub(super) fn find_balanced_json_end(s: &str) -> Option<usize> {
let bytes = s.as_bytes();
if bytes.first() != Some(&b'{') {
return None;
}
let mut depth: i32 = 0;
let mut in_string = false;
let mut escaped = false;
for (i, &b) in bytes.iter().enumerate() {
if in_string {
if escaped {
escaped = false;
} else if b == b'\\' {
escaped = true;
} else if b == b'"' {
in_string = false;
}
continue;
}
match b {
b'"' => in_string = true,
b'{' => depth += 1,
b'}' => {
depth -= 1;
if depth == 0 {
return Some(i + 1);
}
}
_ => {}
}
}
None
}
fn largest_balanced_json_prefix(s: &str) -> Option<String> {
let bytes = s.as_bytes();
if bytes.first() != Some(&b'{') {
return None;
}
let mut depth: i32 = 0;
let mut in_string = false;
let mut escaped = false;
let mut last_valid: Option<usize> = None;
for (i, &b) in bytes.iter().enumerate() {
if in_string {
if escaped {
escaped = false;
} else if b == b'\\' {
escaped = true;
} else if b == b'"' {
in_string = false;
}
continue;
}
match b {
b'"' => in_string = true,
b'{' => depth += 1,
b'}' => {
depth -= 1;
if depth == 0 {
last_valid = Some(i + 1);
break;
}
}
_ => {}
}
}
let end = last_valid?;
let prefix = &s[..end];
serde_json::from_str::<serde_json::Value>(prefix)
.ok()
.and_then(|v| serde_json::to_string(&v).ok())
}
pub(super) fn parse_bare_identifier_json_calls(text: &str) -> (Option<String>, Vec<ToolCall>) {
let mut calls: Vec<ToolCall> = Vec::new();
let mut content_parts: Vec<String> = Vec::new();
let mut last_end = 0usize;
let bytes = text.as_bytes();
let mut i = 0usize;
while i < bytes.len() {
if bytes[i] == b'{' {
let mut start = i;
while start > 0 {
let b = bytes[start - 1];
if (b as char).is_ascii() && is_tool_name_or_namespace_char(b as char) {
start -= 1;
} else {
break;
}
}
let name_bytes = &bytes[start..i];
if name_bytes.len() >= 2
&& (name_bytes[0].is_ascii_alphabetic() || name_bytes[0] == b'_')
&& !matches!(name_bytes,
b"function" | b"object" | b"params" | b"return" | b"const" |
b"let" | b"var" | b"if" | b"else" | b"for" | b"while" | b"class")
{
let suffix = &text[i..];
if let Some(end_rel) = find_balanced_json_end(suffix) {
let json_slice = &suffix[..end_rel];
let parsed = serde_json::from_str::<serde_json::Value>(json_slice)
.ok()
.or_else(|| {
let repaired = repair_bare_object_json(json_slice);
serde_json::from_str::<serde_json::Value>(&repaired).ok()
});
if let Some(v) = parsed {
let raw_name = std::str::from_utf8(name_bytes).unwrap_or("");
let name = normalize_tool_name(raw_name);
if v.is_object() && is_normalized_tool_name(&name) {
let args = serde_json::to_string(&v).unwrap_or_else(|_| "{}".into());
if start > last_end {
let chunk = text[last_end..start].trim();
if !chunk.is_empty() {
content_parts.push(chunk.to_string());
}
}
calls.push(ToolCall {
id: next_tool_call_id(),
call_type: "function".into(),
function: FunctionCall {
name,
arguments: args,
},
});
last_end = i + end_rel;
i = last_end;
continue;
}
}
}
}
}
i += 1;
}
if !calls.is_empty() && last_end < text.len() {
let tail = text[last_end..].trim();
if !tail.is_empty() {
content_parts.push(tail.to_string());
}
}
let content = if content_parts.is_empty() {
None
} else {
Some(content_parts.join("\n"))
};
(content, calls)
}
fn repair_bare_object_json(s: &str) -> String {
let trimmed = s.trim();
if !trimmed.starts_with('{') || !trimmed.ends_with('}') {
return s.to_string();
}
let inner = &trimmed[1..trimmed.len() - 1];
let mut out = String::with_capacity(s.len() + 32);
out.push('{');
let mut first = true;
let mut depth: i32 = 0;
let mut start = 0usize;
let bytes = inner.as_bytes();
let mut in_str = false;
for (i, &b) in bytes.iter().enumerate() {
if in_str {
if b == b'"' && (i == 0 || bytes[i - 1] != b'\\') {
in_str = false;
}
continue;
}
if b == b'"' {
in_str = true;
continue;
}
if b == b'{' || b == b'[' {
depth += 1;
continue;
}
if b == b'}' || b == b']' {
depth -= 1;
continue;
}
if b == b',' && depth == 0 {
if !append_repaired_member(&mut out, &inner[start..i], &mut first) {
return s.to_string();
}
start = i + 1;
}
}
if start < inner.len() && !append_repaired_member(&mut out, &inner[start..], &mut first) {
return s.to_string();
}
out.push('}');
out
}
fn append_repaired_member(out: &mut String, member: &str, first: &mut bool) -> bool {
let m = member.trim();
if m.is_empty() {
return true;
}
let colon = match m.find(':') {
Some(c) => c,
None => return false,
};
let key = m[..colon].trim();
let val = m[colon + 1..].trim();
if key.is_empty() || val.is_empty() {
return false;
}
let key_quoted = if key.starts_with('"') && key.ends_with('"') {
key.to_string()
} else if key
.bytes()
.all(|c| c.is_ascii_alphanumeric() || c == b'_' || c == b'-')
{
format!("\"{key}\"")
} else {
return false;
};
let val_quoted = if val.starts_with('"')
|| val.starts_with('{')
|| val.starts_with('[')
|| val == "true"
|| val == "false"
|| val == "null"
|| val.parse::<f64>().is_ok()
{
val.to_string()
} else {
let escaped = val.replace('"', "\\\"");
format!("\"{escaped}\"")
};
if !*first {
out.push(',');
}
*first = false;
out.push_str(&key_quoted);
out.push(':');
out.push_str(&val_quoted);
true
}
pub(super) fn normalize_tool_name(raw: &str) -> String {
let mut name = raw.trim().trim_matches('"').trim_matches('\'').to_string();
if name.starts_with("name=") || name.starts_with("name =") {
name = name
.trim_start_matches("name")
.trim_start_matches('=')
.trim()
.trim_matches('"')
.trim_matches('\'')
.to_string();
}
if let Some(eq_pos) = name.find('=') {
name = name[..eq_pos].trim().to_string();
}
if let Some(colon) = name.rfind(':') {
let head = &name[..colon];
let tail = &name[colon + 1..];
let head_is_namespace =
!head.is_empty() && head.chars().all(is_tool_name_or_namespace_char);
if head_is_namespace && is_tool_name_component(tail) {
name = tail.to_string();
}
}
name
}