use openai_protocol::{
common::{Function, Tool},
responses::{generate_id, FunctionTool, McpToolInfo, ResponseOutputItem, ResponseTool},
};
use serde_json::{json, Value};
use crate::inventory::{QualifiedToolName, ToolEntry};
#[inline]
fn schema_to_value(schema: &serde_json::Map<String, Value>) -> Value {
Value::Object(schema.clone())
}
fn resolved_name_for_entry<'a>(
entry: &'a ToolEntry,
exposed_names: Option<&'a std::collections::HashMap<QualifiedToolName, String>>,
) -> &'a str {
exposed_names
.and_then(|m| m.get(&entry.qualified_name))
.map(|s| s.as_str())
.unwrap_or_else(|| entry.tool_name())
}
fn resolved_tool_fields<'a>(
entries: &'a [ToolEntry],
exposed_names: Option<&'a std::collections::HashMap<QualifiedToolName, String>>,
) -> impl Iterator<Item = (&'a str, Option<&'a str>, &'a serde_json::Map<String, Value>)> + 'a {
entries.iter().map(move |entry| {
let name = resolved_name_for_entry(entry, exposed_names);
let description = entry.tool.description.as_deref();
(name, description, &*entry.tool.input_schema)
})
}
pub fn build_function_tools_json(entries: &[ToolEntry]) -> Vec<Value> {
build_function_tools_json_with_names(entries, None)
}
pub fn build_function_tools_json_with_names(
entries: &[ToolEntry],
exposed_names: Option<&std::collections::HashMap<QualifiedToolName, String>>,
) -> Vec<Value> {
resolved_tool_fields(entries, exposed_names)
.map(|(name, description, parameters)| {
json!({
"type": "function",
"name": name,
"description": description,
"parameters": schema_to_value(parameters)
})
})
.collect()
}
pub fn build_chat_function_tools(entries: &[ToolEntry]) -> Vec<Tool> {
build_chat_function_tools_with_names(entries, None)
}
pub fn build_chat_function_tools_with_names(
entries: &[ToolEntry],
exposed_names: Option<&std::collections::HashMap<QualifiedToolName, String>>,
) -> Vec<Tool> {
resolved_tool_fields(entries, exposed_names)
.map(|(name, description, parameters)| Tool {
tool_type: "function".to_string(),
function: Function {
name: name.to_string(),
description: description.map(|d| d.to_string()),
parameters: schema_to_value(parameters),
strict: None,
},
})
.collect()
}
pub fn build_response_tools(entries: &[ToolEntry]) -> Vec<ResponseTool> {
build_response_tools_with_names(entries, None)
}
pub fn build_response_tools_with_names(
entries: &[ToolEntry],
exposed_names: Option<&std::collections::HashMap<QualifiedToolName, String>>,
) -> Vec<ResponseTool> {
resolved_tool_fields(entries, exposed_names)
.map(|(name, description, parameters)| {
ResponseTool::Function(FunctionTool {
function: Function {
name: name.to_string(),
description: description.map(|d| d.to_string()),
parameters: schema_to_value(parameters),
strict: None,
},
})
})
.collect()
}
pub fn build_mcp_tool_infos(entries: &[ToolEntry]) -> Vec<McpToolInfo> {
entries
.iter()
.map(|entry| McpToolInfo {
name: entry.tool_name().to_string(),
description: entry.tool.description.as_ref().map(|d| d.to_string()),
input_schema: schema_to_value(&entry.tool.input_schema),
annotations: entry
.tool
.annotations
.as_ref()
.and_then(|a| serde_json::to_value(a).ok()),
})
.collect()
}
pub fn build_mcp_list_tools_item(server_label: &str, entries: &[ToolEntry]) -> ResponseOutputItem {
ResponseOutputItem::McpListTools {
id: generate_id("mcpl"),
server_label: server_label.to_string(),
tools: build_mcp_tool_infos(entries),
}
}
pub fn build_mcp_list_tools_json(server_label: &str, entries: &[ToolEntry]) -> Value {
serde_json::to_value(build_mcp_list_tools_item(server_label, entries)).unwrap_or_else(
|_| json!({ "type": "mcp_list_tools", "server_label": server_label, "tools": [] }),
)
}