use serde_json::{Value, json};
use super::call::{GuardedToolCall, InteropError, ToolCallRequest};
use super::wire;
pub const DIALECT: &str = "openai";
pub fn parse_tool_calls(payload: &Value) -> Result<Vec<ToolCallRequest>, InteropError> {
if let Some(choices) = payload.get("choices").and_then(Value::as_array) {
let mut calls = Vec::new();
for choice in choices {
if let Some(items) = choice
.pointer("/message/tool_calls")
.and_then(Value::as_array)
{
for item in items {
calls.push(parse_call(item)?);
}
}
}
return Ok(calls);
}
wire::call_array(payload, "tool_calls", DIALECT)?
.iter()
.map(parse_call)
.collect()
}
fn parse_call(item: &Value) -> Result<ToolCallRequest, InteropError> {
let function = item.get("function").ok_or_else(|| {
InteropError::malformed(DIALECT, "tool call entry has no 'function' object")
})?;
let name = wire::required_str(function, "name", DIALECT)?;
let arguments = wire::parse_arguments(function.get("arguments"), DIALECT, name)?;
let mut request = ToolCallRequest::new(name, arguments);
if let Some(id) = wire::optional_str(item, "id") {
request = request.with_call_id(id);
}
Ok(request)
}
pub fn filter_tools(tools: &Value, keep: &dyn Fn(&str) -> bool) -> Value {
wire::filter_tool_array(
tools,
|item| {
item.pointer("/function/name")
.or_else(|| item.get("name"))
.and_then(Value::as_str)
.map(str::to_owned)
},
keep,
)
}
pub fn denial(call: &GuardedToolCall) -> Option<Value> {
call.denial_message().map(|content| {
json!({
"role": "tool",
"tool_call_id": call.request.call_id.clone().unwrap_or_default(),
"content": content,
})
})
}