use crate::tool_parser::{DetectorOutput, StreamingToolDetector};
pub(super) fn indexed_trace(outputs: &[DetectorOutput]) -> Vec<(&'static str, usize)> {
outputs
.iter()
.filter_map(|o| match o {
DetectorOutput::ToolCallStart { idx, .. } => Some(("start", *idx)),
DetectorOutput::ToolCallEnd { idx } => Some(("end", *idx)),
DetectorOutput::ToolCall(_, idx) => Some(("call", *idx)),
_ => None,
})
.collect()
}
pub(super) fn name_for_idx(outputs: &[DetectorOutput], want: usize) -> Option<String> {
outputs.iter().find_map(|o| match o {
DetectorOutput::ToolCallStart { name, idx, .. } if *idx == want => Some(name.clone()),
DetectorOutput::ToolCall(tc, idx) if *idx == want => Some(tc.function.name.clone()),
_ => None,
})
}
pub(super) fn args_for_idx(outputs: &[DetectorOutput], want: usize) -> String {
let frags: String = outputs
.iter()
.filter_map(|o| match o {
DetectorOutput::ToolCallArgsFragment { fragment, idx } if *idx == want => {
Some(fragment.as_str())
}
_ => None,
})
.collect();
if !frags.is_empty() {
return frags;
}
outputs
.iter()
.find_map(|o| match o {
DetectorOutput::ToolCallDelta { args, idx } if *idx == want => Some(args.clone()),
DetectorOutput::ToolCall(tc, idx) if *idx == want => {
Some(tc.function.arguments.clone())
}
_ => None,
})
.unwrap_or_default()
}
pub(super) fn assert_json_eq(actual: &str, expected: &str, ctx: &str) {
let a: serde_json::Value =
serde_json::from_str(actual).unwrap_or_else(|e| panic!("{ctx}: bad JSON {actual:?}: {e}"));
let b: serde_json::Value = serde_json::from_str(expected).unwrap();
assert_eq!(a, b, "{ctx}: args mismatch");
}
pub(super) fn drive_chunked(
det: &mut StreamingToolDetector,
text: &str,
chunk: usize,
) -> Vec<DetectorOutput> {
let mut outputs = Vec::new();
let mut i = 0;
while i < text.len() {
let end = (i + chunk).min(text.len());
outputs.extend(det.process(&text[i..end]));
i = end;
}
outputs.extend(det.flush());
outputs
}
pub(super) fn hermes_call(name: &str, args: &str) -> String {
format!("<tool_call>\n{{\"name\": \"{name}\", \"arguments\": {args}}}\n</tool_call>")
}
pub(super) fn qwen3_coder_call(name: &str, params: &[(&str, &str)]) -> String {
let mut s = format!("<tool_call>\n<function={name}>\n");
for (k, v) in params {
s.push_str(&format!("<parameter={k}>\n{v}\n</parameter>\n"));
}
s.push_str("</function>\n</tool_call>");
s
}