2026-07-12 11:28:39 +07:00
|
|
|
//! Standalone accumulator for streamed tool-call deltas.
|
|
|
|
|
//!
|
|
|
|
|
//! Flow: `ToolCallAccumulator::add_delta` is fed incremental `(index, id,
|
|
|
|
|
//! name, arguments_delta)` chunks as they arrive over SSE → grows its
|
|
|
|
|
//! internal `Vec<ParsedToolCall>` as needed → `is_complete` reports once
|
|
|
|
|
//! every accumulated call has both a name and arguments.
|
|
|
|
|
//!
|
|
|
|
|
//! Why: mirrors the accumulation logic built into `StreamedTurn::apply_event`
|
|
|
|
|
//! but as an independent, reusable type for callers that want to track
|
|
|
|
|
//! tool-call deltas without a full `StreamedTurn` (e.g. a lighter-weight
|
|
|
|
|
//! preview). Currently unused (`#[allow(dead_code)]`), kept for that future
|
|
|
|
|
//! use case.
|
|
|
|
|
|
2026-07-11 23:45:13 +07:00
|
|
|
use super::turn::ParsedToolCall;
|
|
|
|
|
use serde_json::{json, Value};
|
|
|
|
|
|
2026-07-12 01:25:52 +07:00
|
|
|
/// Standalone tool-call delta accumulator, functionally equivalent to the accumulation
|
|
|
|
|
/// logic built into `StreamedTurn::apply_event`. Reserved for callers that want to track
|
|
|
|
|
/// tool-call deltas independently of a full `StreamedTurn` (e.g. a lighter-weight preview).
|
|
|
|
|
#[allow(dead_code)]
|
2026-07-11 23:45:13 +07:00
|
|
|
pub struct ToolCallAccumulator {
|
|
|
|
|
calls: Vec<ParsedToolCall>,
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-12 01:25:52 +07:00
|
|
|
#[allow(dead_code)]
|
2026-07-11 23:45:13 +07:00
|
|
|
impl ToolCallAccumulator {
|
2026-07-12 11:28:39 +07:00
|
|
|
/// Construct an empty accumulator with no tool calls tracked yet.
|
|
|
|
|
///
|
|
|
|
|
/// Return: a fresh `ToolCallAccumulator`.
|
2026-07-11 23:45:13 +07:00
|
|
|
pub fn new() -> Self {
|
|
|
|
|
ToolCallAccumulator { calls: Vec::new() }
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-12 11:28:39 +07:00
|
|
|
/// Append a delta to the tool call at the given index, growing the
|
|
|
|
|
/// calls vector if needed.
|
2026-07-11 23:45:13 +07:00
|
|
|
pub fn add_delta(
|
|
|
|
|
&mut self,
|
|
|
|
|
index: usize,
|
|
|
|
|
id: Option<&str>,
|
|
|
|
|
name: Option<&str>,
|
|
|
|
|
arguments_delta: &str,
|
|
|
|
|
) {
|
|
|
|
|
while self.calls.len() <= index {
|
|
|
|
|
self.calls.push(ParsedToolCall {
|
|
|
|
|
id: String::new(),
|
|
|
|
|
name: String::new(),
|
|
|
|
|
arguments: String::new(),
|
|
|
|
|
is_complete: false,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
let tc = &mut self.calls[index];
|
|
|
|
|
if let Some(new_id) = id {
|
|
|
|
|
if !new_id.is_empty() {
|
|
|
|
|
tc.id = new_id.to_string();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if let Some(new_name) = name {
|
|
|
|
|
if !new_name.is_empty() {
|
|
|
|
|
tc.name = new_name.to_string();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
tc.arguments.push_str(arguments_delta);
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-12 11:28:39 +07:00
|
|
|
/// Borrow the accumulated tool calls.
|
2026-07-11 23:45:13 +07:00
|
|
|
pub fn calls(&self) -> &[ParsedToolCall] {
|
|
|
|
|
&self.calls
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-12 11:28:39 +07:00
|
|
|
/// Return true once all tool calls have both a name and arguments.
|
2026-07-11 23:45:13 +07:00
|
|
|
pub fn is_complete(&self) -> bool {
|
|
|
|
|
!self.calls.is_empty() && self.calls.iter().all(|tc| !tc.name.is_empty() && !tc.arguments.is_empty())
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-12 11:28:39 +07:00
|
|
|
/// Clear all accumulated calls (starting a fresh turn).
|
2026-07-11 23:45:13 +07:00
|
|
|
pub fn reset(&mut self) {
|
|
|
|
|
self.calls.clear();
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-12 11:28:39 +07:00
|
|
|
/// Build a JSON-serialisable `Vec<Value>` of pending (non-empty-name)
|
|
|
|
|
/// tool calls, suitable for downstream inspection or replay.
|
2026-07-11 23:45:13 +07:00
|
|
|
pub fn pending_args(&self) -> Vec<Value> {
|
|
|
|
|
self.calls
|
|
|
|
|
.iter()
|
|
|
|
|
.filter(|tc| !tc.name.is_empty())
|
|
|
|
|
.map(|tc| {
|
|
|
|
|
json!({
|
|
|
|
|
"tool_call_id": tc.id,
|
|
|
|
|
"name": tc.name,
|
|
|
|
|
"arguments": tc.arguments,
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
.collect()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Default for ToolCallAccumulator {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
Self::new()
|
|
|
|
|
}
|
|
|
|
|
}
|