use super::super::{text_tool_call_block, TEXT_TOOL_CALL_TAG};
use super::syntax::{match_tool_call_block, render_canonical_call};
use super::tagged::{
inside_markdown_fence, is_top_level_tag_position, leading_call_name, parse_single_tool_call,
unclosed_tool_call_open,
};
use crate::value::VmValue;
const WIRE_MALFORMED_CALL_OPENER: &str = "[[CALL]";
pub(super) fn recover_malformed_call_opener(
src: &str,
cursor: usize,
tools_val: Option<&VmValue>,
calls: &mut Vec<serde_json::Value>,
errors: &mut Vec<String>,
canonical_parts: &mut Vec<String>,
) -> Option<usize> {
let tail = src[cursor..].strip_prefix(WIRE_MALFORMED_CALL_OPENER)?;
if tail.starts_with(']') {
return None;
}
if !is_top_level_tag_position(src, cursor) || inside_markdown_fence(src, cursor) {
return None;
}
let canonical_open = format!("<{TEXT_TOOL_CALL_TAG}>");
let delta = canonical_open.len() - WIRE_MALFORMED_CALL_OPENER.len();
let synthetic = format!("{canonical_open}{tail}");
if let Some((body, after)) = match_tool_call_block(&synthetic, 0, TEXT_TOOL_CALL_TAG) {
match parse_single_tool_call(body, tools_val) {
Ok(call) => {
let name = call
.get("name")
.and_then(|name| name.as_str())
.unwrap_or("")
.to_string();
let args = call
.get("arguments")
.cloned()
.unwrap_or(serde_json::json!({}));
canonical_parts.push(text_tool_call_block(&render_canonical_call(&name, &args)));
calls.push(call);
}
Err(msg) => errors.push(msg),
}
return Some(cursor + after - delta);
}
let truncated_body =
unclosed_tool_call_open(&synthetic, 0).map(|open_len| &synthetic[open_len..]);
match truncated_body.and_then(|body| leading_call_name(body, tools_val)) {
Some(name) => errors.push(format!(
"TOOL CALL TRUNCATED: `<tool_call>` opening `{name}(...)` was never \
closed — the response appears to have been cut off mid-call (likely \
the model hit its max output token limit). Re-emit the complete \
`<tool_call>{name}({{ ... }})</tool_call>` block; for very large \
arguments, split the work into smaller calls."
)),
None => errors.push(
"TOOL CALL TRUNCATED: a `<tool_call>` block was opened but never \
closed — the response appears to have been cut off (likely the model \
hit its max output token limit). Re-emit the complete \
`<tool_call>name({ ... })</tool_call>` block, splitting very large \
arguments into smaller calls if needed."
.to_string(),
),
}
Some(src.len())
}