use crate::types::{Event, ToolCall};
fn collect_openai_tool_events(frames: &[(&str, &str)]) -> Vec<Event> {
use std::collections::HashMap;
let mut tool_calls: HashMap<String, (Option<String>, String)> = HashMap::new();
let mut out = Vec::new();
for (event, data) in frames {
match *event {
"response.output_item.added" => {
let v: serde_json::Value = serde_json::from_str(data).unwrap();
let item = v.get("item").unwrap();
if item.get("type").and_then(|x| x.as_str()) == Some("function_call") {
let id = item.get("id").and_then(|x| x.as_str()).unwrap();
let name = item
.get("name")
.and_then(|x| x.as_str())
.map(|s| s.to_string());
let args = item
.get("arguments")
.and_then(|x| x.as_str())
.unwrap_or("")
.to_string();
tool_calls.insert(id.to_string(), (name, args));
}
}
"response.function_call_arguments.delta" => {
let v: serde_json::Value = serde_json::from_str(data).unwrap();
let call_id = v.get("call_id").and_then(|x| x.as_str()).unwrap();
let delta = v.get("delta").and_then(|x| x.as_str()).unwrap();
let entry = tool_calls
.entry(call_id.to_string())
.or_insert((None, String::new()));
entry.1.push_str(delta);
}
"response.function_call_arguments.done" => {
let v: serde_json::Value = serde_json::from_str(data).unwrap();
let call_id = v.get("call_id").and_then(|x| x.as_str()).unwrap();
if let Some((name_opt, raw_args)) = tool_calls.remove(call_id) {
let name = name_opt.unwrap_or_else(|| "tool".to_string());
let args: serde_json::Value = serde_json::from_str(&raw_args).unwrap();
out.push(Event::ToolCall(ToolCall {
id: Some(call_id.to_string()),
name,
arguments: args,
}));
}
}
_ => {}
}
}
out
}
#[test]
fn openai_stream_assembles_tool_call_args() {
let frames = vec![
(
"response.output_item.added",
r#"{"item":{"type":"function_call","id":"call_1","name":"get_weather","arguments":""}}"#,
),
(
"response.function_call_arguments.delta",
r#"{"call_id":"call_1","delta":"{\"city\":\"Ber"}"#,
),
(
"response.function_call_arguments.delta",
r#"{"call_id":"call_1","delta":"lin\",\"units\":\"c\"}"}"#,
),
(
"response.function_call_arguments.done",
r#"{"call_id":"call_1"}"#,
),
];
let events = collect_openai_tool_events(&frames);
assert_eq!(events.len(), 1);
let Event::ToolCall(call) = &events[0] else {
panic!("expected ToolCall event");
};
assert_eq!(call.id.as_deref(), Some("call_1"));
assert_eq!(call.name, "get_weather");
assert_eq!(call.arguments["city"].as_str(), Some("Berlin"));
assert_eq!(call.arguments["units"].as_str(), Some("c"));
}