Skip to main content

compose_tools/
lib.rs

1#[derive(Clone, Debug, Eq, PartialEq)]
2pub struct ToolCall {
3    pub id: String,
4    pub name: String,
5    pub arguments: String,
6}
7
8#[derive(Clone, Debug, Default, Eq, PartialEq)]
9pub struct ToolAccumulator {
10    calls: Vec<ToolCall>,
11}
12
13impl ToolAccumulator {
14    pub fn new() -> Self {
15        Self::default()
16    }
17
18    pub fn apply_delta(&mut self, id: &str, name: Option<&str>, arguments: Option<&str>) {
19        if let Some(call) = self.calls.iter_mut().find(|call| call.id == id) {
20            if let Some(name) = name {
21                if !name.is_empty() {
22                    call.name = name.to_string();
23                }
24            }
25            if let Some(arguments) = arguments {
26                call.arguments.push_str(arguments);
27            }
28            return;
29        }
30
31        self.calls.push(ToolCall {
32            id: id.to_string(),
33            name: name.unwrap_or_default().to_string(),
34            arguments: arguments.unwrap_or_default().to_string(),
35        });
36    }
37
38    pub fn calls(&self) -> &[ToolCall] {
39        &self.calls
40    }
41}
42
43#[cfg(test)]
44mod tests {
45    use super::ToolAccumulator;
46
47    #[test]
48    fn accumulates_split_arguments() {
49        let mut accumulator = ToolAccumulator::new();
50        accumulator.apply_delta("tc_1", Some("search"), Some("{\"q\":"));
51        accumulator.apply_delta("tc_1", None, Some("\"kittens\"}"));
52
53        assert_eq!(accumulator.calls()[0].arguments, "{\"q\":\"kittens\"}");
54    }
55}