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}