use std::rc::Rc;
use super::json_schema::vm_build_json_schema;
use crate::value::{VmError, VmValue};
pub(crate) fn vm_tools_to_native(
tools_val: &VmValue,
provider: &str,
) -> Result<Vec<serde_json::Value>, VmError> {
let tools_list = match tools_val {
VmValue::Dict(dict) => match dict.get("tools") {
Some(VmValue::List(list)) => list.as_ref().clone(),
_ => Vec::new(),
},
VmValue::List(list) => list.as_ref().clone(),
_ => {
return Err(VmError::Thrown(VmValue::String(Rc::from(
"tools must be a tool_registry or a list of tool definition dicts",
))));
}
};
let mut native_tools = Vec::new();
for tool in &tools_list {
match tool {
VmValue::Dict(entry) => {
let name = entry
.get("name")
.map(|value| value.display())
.unwrap_or_default();
let description = entry
.get("description")
.map(|value| value.display())
.unwrap_or_default();
let params = entry.get("parameters").and_then(|value| value.as_dict());
let output_schema = entry
.get("outputSchema")
.map(super::super::vm_value_to_json);
let defer_loading = matches!(entry.get("defer_loading"), Some(VmValue::Bool(true)));
let namespace = entry.get("namespace").and_then(|value| match value {
VmValue::String(string) if !string.is_empty() => Some(string.to_string()),
_ => None,
});
let input_schema = vm_build_json_schema(params);
let is_anthropic =
super::super::helpers::ResolvedProvider::resolve(provider).is_anthropic_style;
if is_anthropic {
let mut tool_json = serde_json::json!({
"name": name,
"description": description,
"input_schema": input_schema,
});
if let Some(output_schema) = output_schema {
tool_json["x-harn-output-schema"] = output_schema;
}
if defer_loading {
tool_json["defer_loading"] = serde_json::Value::Bool(true);
}
if let Some(ns) = namespace {
tool_json["namespace"] = serde_json::Value::String(ns);
}
native_tools.push(tool_json);
} else {
let mut tool_json = serde_json::json!({
"type": "function",
"function": {
"name": name,
"description": description,
"parameters": input_schema,
}
});
if let Some(output_schema) = output_schema {
tool_json["function"]["x-harn-output-schema"] = output_schema;
}
if defer_loading {
tool_json["defer_loading"] = serde_json::Value::Bool(true);
}
if let Some(ns) = namespace {
tool_json["namespace"] = serde_json::Value::String(ns);
}
native_tools.push(tool_json);
}
}
VmValue::String(_) => {
return Err(VmError::Thrown(VmValue::String(Rc::from(
"tools must be declared as tool definition dicts or a tool_registry",
))));
}
_ => {
return Err(VmError::Thrown(VmValue::String(Rc::from(
"tools must contain only tool definition dicts",
))));
}
}
}
Ok(native_tools)
}
pub(crate) fn extract_deferred_tool_names(native_tools: &[serde_json::Value]) -> Vec<String> {
native_tools
.iter()
.filter_map(|tool| {
if tool.get("defer_loading").and_then(|value| value.as_bool()) == Some(true) {
if let Some(name) = tool.get("name").and_then(|value| value.as_str()) {
return Some(name.to_string());
}
if let Some(name) = tool
.get("function")
.and_then(|function| function.get("name"))
.and_then(|value| value.as_str())
{
return Some(name.to_string());
}
}
None
})
.collect()
}
#[cfg(test)]
pub(crate) fn apply_tool_search_native_injection(
native_tools: &mut Option<Vec<serde_json::Value>>,
provider: &str,
variant: &str,
) {
let shape = if provider == "anthropic" {
super::super::provider::NativeToolSearchShape::Anthropic
} else {
super::super::provider::NativeToolSearchShape::OpenAi
};
apply_tool_search_native_injection_typed(native_tools, shape, variant, "hosted");
}
pub(crate) fn apply_tool_search_native_injection_typed(
native_tools: &mut Option<Vec<serde_json::Value>>,
shape: super::super::provider::NativeToolSearchShape,
variant: &str,
mode: &str,
) {
use super::super::provider::NativeToolSearchShape;
match shape {
NativeToolSearchShape::Anthropic => {
let (type_name, tool_name) = match variant {
"regex" => ("tool_search_tool_regex_20251119", "tool_search_tool_regex"),
_ => ("tool_search_tool_bm25_20251119", "tool_search_tool_bm25"),
};
let meta = serde_json::json!({
"type": type_name,
"name": tool_name,
});
prepend_meta_tool(native_tools, meta);
}
NativeToolSearchShape::OpenAi => {
let resolved_mode = if mode == "client" { "client" } else { "hosted" };
let mut meta = serde_json::json!({
"type": "tool_search",
"mode": resolved_mode,
});
if let Some(tools) = native_tools.as_ref() {
let mut namespaces: Vec<String> = tools.iter().filter_map(tool_namespace).collect();
namespaces.sort();
namespaces.dedup();
if !namespaces.is_empty() {
meta["namespaces"] = serde_json::json!(namespaces);
}
}
prepend_meta_tool(native_tools, meta);
}
}
}
fn prepend_meta_tool(native_tools: &mut Option<Vec<serde_json::Value>>, meta: serde_json::Value) {
match native_tools {
Some(list) => list.insert(0, meta),
None => *native_tools = Some(vec![meta]),
}
}
pub(crate) fn tool_namespace(tool: &serde_json::Value) -> Option<String> {
tool.get("namespace")
.and_then(|value| value.as_str())
.or_else(|| {
tool.get("function")
.and_then(|function| function.get("namespace"))
.and_then(|value| value.as_str())
})
.map(|value| value.to_string())
}