use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
pub struct ToolCallRequest {
pub name: String,
pub arguments: Value,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tool_call_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub progress_token: Option<Value>,
}
impl ToolCallRequest {
pub fn new(name: impl Into<String>, arguments: Value) -> Self {
Self {
name: name.into(),
arguments,
tool_call_id: None,
progress_token: None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn omitted_optionals_decode_as_none() {
let request: ToolCallRequest =
serde_json::from_value(json!({ "name": "grep", "arguments": { "q": "x" } }))
.expect("two-field body decodes");
assert_eq!(request.tool_call_id, None);
assert_eq!(request.progress_token, None);
}
#[test]
fn none_optionals_are_omitted_so_the_wire_matches_the_two_field_shape() {
let request = ToolCallRequest::new("grep", json!({ "q": "x" }));
let encoded = serde_json::to_value(&request).expect("encode");
assert_eq!(
encoded,
json!({ "name": "grep", "arguments": { "q": "x" } })
);
}
#[test]
fn tool_call_id_round_trips() {
let request = ToolCallRequest {
name: "grep".to_string(),
arguments: json!({ "q": "x" }),
tool_call_id: Some("wal-intent-42".to_string()),
progress_token: None,
};
let encoded = serde_json::to_value(&request).expect("encode");
assert_eq!(encoded["tool_call_id"], json!("wal-intent-42"));
let decoded: ToolCallRequest = serde_json::from_value(encoded).expect("decode");
assert_eq!(decoded, request);
}
#[test]
fn unknown_members_do_not_fail_a_provider_decode() {
let request: ToolCallRequest = serde_json::from_value(json!({
"name": "grep",
"arguments": {},
"some_future_key": { "nested": true }
}))
.expect("unknown members are tolerated");
assert_eq!(request.name, "grep");
}
}