typesec_agent/interop/
openai.rs1use serde_json::{Value, json};
5
6use super::call::{GuardedToolCall, InteropError, ToolCallRequest};
7use super::wire;
8
9pub const DIALECT: &str = "openai";
11
12pub fn parse_tool_calls(payload: &Value) -> Result<Vec<ToolCallRequest>, InteropError> {
16 if let Some(choices) = payload.get("choices").and_then(Value::as_array) {
17 let mut calls = Vec::new();
18 for choice in choices {
19 if let Some(items) = choice
20 .pointer("/message/tool_calls")
21 .and_then(Value::as_array)
22 {
23 for item in items {
24 calls.push(parse_call(item)?);
25 }
26 }
27 }
28 return Ok(calls);
29 }
30 wire::call_array(payload, "tool_calls", DIALECT)?
31 .iter()
32 .map(parse_call)
33 .collect()
34}
35
36fn parse_call(item: &Value) -> Result<ToolCallRequest, InteropError> {
37 let function = item.get("function").ok_or_else(|| {
38 InteropError::malformed(DIALECT, "tool call entry has no 'function' object")
39 })?;
40 let name = wire::required_str(function, "name", DIALECT)?;
41 let arguments = wire::parse_arguments(function.get("arguments"), DIALECT, name)?;
42 let mut request = ToolCallRequest::new(name, arguments);
43 if let Some(id) = wire::optional_str(item, "id") {
44 request = request.with_call_id(id);
45 }
46 Ok(request)
47}
48
49pub fn filter_tools(tools: &Value, keep: &dyn Fn(&str) -> bool) -> Value {
54 wire::filter_tool_array(
55 tools,
56 |item| {
57 item.pointer("/function/name")
58 .or_else(|| item.get("name"))
59 .and_then(Value::as_str)
60 .map(str::to_owned)
61 },
62 keep,
63 )
64}
65
66pub fn denial(call: &GuardedToolCall) -> Option<Value> {
70 call.denial_message().map(|content| {
71 json!({
72 "role": "tool",
73 "tool_call_id": call.request.call_id.clone().unwrap_or_default(),
74 "content": content,
75 })
76 })
77}