use serde_json::Value;
#[derive(Debug, Clone)]
pub(crate) struct ParsedToolCall {
pub name: String,
pub args: Value,
}
#[derive(Debug, Clone)]
pub(crate) enum ParseResult {
Success(ParsedToolCall),
Reject(&'static str),
}
pub(crate) trait TextualToolParser: Send + Sync {
fn name(&self) -> &'static str;
fn try_parse(&self, text: &str) -> ParseResult;
fn should_validate_tool_name(&self) -> bool {
true
}
fn find_consumed_spans(&self, _text: &str) -> Vec<(usize, usize)> {
Vec::new()
}
}
pub(crate) struct TextualToolParserRegistry {
parsers: Vec<Box<dyn TextualToolParser>>,
}
impl TextualToolParserRegistry {
pub(crate) fn new() -> Self {
Self { parsers: Vec::new() }
}
pub(crate) fn register(&mut self, parser: Box<dyn TextualToolParser>) {
self.parsers.push(parser);
}
pub(crate) fn try_parse(&self, text: &str) -> Option<(ParsedToolCall, bool)> {
for parser in &self.parsers {
match parser.try_parse(text) {
ParseResult::Success(call) => {
tracing::debug!(
parser = parser.name(),
tool_name = %call.name,
"Parser successfully extracted tool call"
);
return Some((call, parser.should_validate_tool_name()));
}
ParseResult::Reject(reason) => {
tracing::debug!(parser = parser.name(), reason, "Parser rejected textual tool call");
}
}
}
None
}
pub(crate) fn consumed_spans(&self, text: &str) -> Vec<(usize, usize)> {
let mut spans = Vec::new();
for parser in &self.parsers {
spans.extend(parser.find_consumed_spans(text));
}
spans
}
}