use crate::llm::tools::{TEXT_TOOL_CALL_CLOSE, TEXT_TOOL_CALL_OPEN};
pub(crate) const WIRE_TOOL_CALL_OPEN: &str = "[[CALL]]";
pub(crate) const WIRE_TOOL_CALL_CLOSE: &str = "[[/CALL]]";
pub(crate) fn canonical_to_wire(text: &str) -> String {
if !text.contains(TEXT_TOOL_CALL_OPEN) && !text.contains(TEXT_TOOL_CALL_CLOSE) {
return text.to_string();
}
text.replace(TEXT_TOOL_CALL_OPEN, WIRE_TOOL_CALL_OPEN)
.replace(TEXT_TOOL_CALL_CLOSE, WIRE_TOOL_CALL_CLOSE)
}
pub(crate) fn wire_to_canonical(text: &str) -> String {
if !text.contains(WIRE_TOOL_CALL_OPEN) && !text.contains(WIRE_TOOL_CALL_CLOSE) {
return text.to_string();
}
text.replace(WIRE_TOOL_CALL_CLOSE, TEXT_TOOL_CALL_CLOSE)
.replace(WIRE_TOOL_CALL_OPEN, TEXT_TOOL_CALL_OPEN)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trips_canonical_delimiters() {
let canonical = "<tool_call>\nlook({ file: \"src\" })\n</tool_call>";
let wire = canonical_to_wire(canonical);
assert_eq!(wire, "[[CALL]]\nlook({ file: \"src\" })\n[[/CALL]]");
assert_eq!(wire_to_canonical(&wire), canonical);
}
#[test]
fn leaves_text_without_delimiters_untouched() {
let plain = "no tool calls here, just prose and code: let x = [[a]];";
assert_eq!(canonical_to_wire(plain), plain);
assert_eq!(wire_to_canonical(plain), plain);
}
#[test]
fn rewrites_every_occurrence() {
let canonical = "<tool_call>a</tool_call> then <tool_call>b</tool_call>";
let wire = canonical_to_wire(canonical);
assert_eq!(wire.matches(WIRE_TOOL_CALL_OPEN).count(), 2);
assert_eq!(wire.matches(WIRE_TOOL_CALL_CLOSE).count(), 2);
assert_eq!(wire_to_canonical(&wire), canonical);
}
#[test]
fn wire_form_is_invisible_until_canonicalized() {
let wire = include_str!("testdata/qwen36_reserved_token_response.txt");
assert!(wire.contains(WIRE_TOOL_CALL_OPEN) && !wire.contains(TEXT_TOOL_CALL_OPEN));
let canonical = wire_to_canonical(wire);
assert!(
canonical.contains(TEXT_TOOL_CALL_OPEN) && !canonical.contains(WIRE_TOOL_CALL_OPEN)
);
assert_eq!(wire_to_canonical(&canonical), canonical);
}
}