use serde_json::{Value, json};
use super::call::{GuardedToolCall, InteropError, ToolBinding, ToolCallRequest};
use super::wire;
pub const DIALECT: &str = "pydantic-ai";
pub const METADATA_PERMISSION_KEY: &str = "typesec_required_permission";
pub const METADATA_RESOURCE_KEY: &str = "typesec_resource_id";
pub fn parse_tool_calls(payload: &Value) -> Result<Vec<ToolCallRequest>, InteropError> {
wire::call_array(payload, "parts", DIALECT)?
.iter()
.filter(|part| part.get("part_kind").and_then(Value::as_str) == Some("tool-call"))
.map(parse_part)
.collect()
}
fn parse_part(part: &Value) -> Result<ToolCallRequest, InteropError> {
let name = wire::required_str(part, "tool_name", DIALECT)?;
let arguments = wire::parse_arguments(part.get("args"), DIALECT, name)?;
let mut request = ToolCallRequest::new(name, arguments);
if let Some(id) = wire::optional_str(part, "tool_call_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| wire::optional_str(item, "name"), keep)
}
pub fn denial(call: &GuardedToolCall) -> Option<Value> {
call.denial_message().map(|content| {
json!({
"part_kind": "retry-prompt",
"tool_name": call.request.tool_name.clone(),
"tool_call_id": call.request.call_id.clone().unwrap_or_default(),
"content": content,
})
})
}
pub fn binding_from_metadata(tool_name: &str, metadata: &Value) -> Option<ToolBinding> {
let action = metadata.get(METADATA_PERMISSION_KEY)?.as_str()?;
let resource = metadata.get(METADATA_RESOURCE_KEY)?.as_str()?;
Some(ToolBinding::new(tool_name, action, resource))
}