use serde_json::{json, Map, Value};
use crate::compat::{IncludeOmit, OpenAIResponsesBuiltinTools, ResolvedOpenAIResponsesCompat};
use crate::types::{BuiltinTool, Request, Tool, ToolChoiceMode};
const OPENAI_BUILTIN_MAP: &[(&str, &str)] = &[
("web_search", "web_search_preview"),
("code_execution", "code_interpreter"),
("file_search", "file_search"),
("computer_use", "computer_use_preview"),
];
pub fn builtin_type<'a>(tool: &'a BuiltinTool, compat: &ResolvedOpenAIResponsesCompat) -> &'a str {
let table: &[(&str, &str)] = match compat.builtin_tools {
OpenAIResponsesBuiltinTools::OpenAI => OPENAI_BUILTIN_MAP,
OpenAIResponsesBuiltinTools::Verbatim => &[],
};
table
.iter()
.find(|(name, _)| *name == tool.name)
.map(|(_, wire)| *wire)
.unwrap_or(&tool.name)
}
pub fn tools_payload(tools: &[Tool], compat: &ResolvedOpenAIResponsesCompat) -> Vec<Value> {
tools
.iter()
.map(|tool| match tool {
Tool::Function(function) => {
let mut payload = Map::new();
payload.insert("type".into(), Value::String("function".into()));
payload.insert("name".into(), Value::String(function.name.clone()));
function.insert_wire_description(&mut payload);
payload.insert(
"parameters".into(),
Value::Object(function.parameters.clone()),
);
if compat.strict_tools == IncludeOmit::Include {
payload.insert("strict".into(), Value::Bool(false));
}
Value::Object(payload)
}
Tool::Builtin(builtin) => builtin_payload(builtin, compat),
})
.collect()
}
fn builtin_payload(tool: &BuiltinTool, compat: &ResolvedOpenAIResponsesCompat) -> Value {
let mut payload = Map::new();
payload.insert(
"type".into(),
Value::String(builtin_type(tool, compat).into()),
);
if let Some(config) = &tool.config {
for (key, value) in config {
payload.insert(key.clone(), value.clone());
}
}
Value::Object(payload)
}
pub fn tool_choice_payload(
request: &Request,
compat: &ResolvedOpenAIResponsesCompat,
) -> Option<Value> {
let choice = request.config.tool_choice.as_ref()?;
if choice.mode == ToolChoiceMode::None {
return Some(json!("none"));
}
if !choice.allowed.is_empty() {
let entries: Vec<&Tool> = choice
.allowed
.iter()
.filter_map(|name| request.tools.iter().find(|tool| tool.name() == name))
.collect();
let forced = |tool: &Tool| match tool {
Tool::Builtin(builtin) => json!({"type": builtin_type(builtin, compat)}),
Tool::Function(function) => json!({"type": "function", "name": function.name}),
};
if entries.len() == 1 && choice.mode == ToolChoiceMode::Required {
return Some(forced(entries[0]));
}
let tools: Vec<Value> = entries.into_iter().map(forced).collect();
return Some(json!({
"type": "allowed_tools",
"mode": choice.mode.as_str(),
"tools": tools,
}));
}
Some(json!(choice.mode.as_str()))
}