use crate::error::{Error, Result};
use serde_json::Value;
mod normalized;
pub use normalized::{AdditionalProperties, SchemaType, ToolParametersSchema};
pub fn fix_output_schema_for_provider(schema: &mut Value, provider: &str) {
match provider {
"openai" | "openrouter" | "openai-responses" | "openrouter-responses" => {
fix_schema_for_openai(schema)
}
"anthropic" | "bedrock" | "google-anthropic" | "vertexai" => {
fix_schema_for_anthropic(schema)
}
"google" | "google_gemini" | "vertex_gemini" | "gemini" | "google-genai"
| "google-gemini" => fix_schema_for_google(schema),
_ => {
fix_schema_for_openai(schema)
}
}
}
fn fix_schema_for_openai(schema: &mut Value) {
if let Value::Object(map) = schema {
if map.get("type") == Some(&Value::String("object".to_string())) {
map.insert("additionalProperties".to_string(), Value::Bool(false));
if let Some(Value::Object(props)) = map.get("properties") {
let all_keys: Vec<Value> = props.keys().map(|k| Value::String(k.clone())).collect();
map.insert("required".to_string(), Value::Array(all_keys));
}
}
recurse_schema_fix(schema, fix_schema_for_openai);
}
}
fn fix_schema_for_anthropic(schema: &mut Value) {
if let Value::Object(map) = schema {
if map.get("type") == Some(&Value::String("object".to_string())) {
map.insert("additionalProperties".to_string(), Value::Bool(false));
}
recurse_schema_fix(schema, fix_schema_for_anthropic);
}
}
fn fix_schema_for_google(schema: &mut Value) {
inline_schema_refs(schema);
if let Value::Object(map) = schema {
map.remove("additionalProperties");
map.remove("$defs");
map.remove("definitions");
recurse_schema_fix(schema, fix_schema_for_google);
}
}
pub fn fix_tool_schema_for_provider(schema: &mut Value, provider: &str) {
match provider {
"google" | "google_gemini" | "vertex_gemini" | "gemini" | "google-genai"
| "google-gemini" => fix_schema_for_google(schema),
_ => {}
}
}
fn inline_schema_refs(schema: &mut Value) {
let defs = if let Value::Object(map) = schema {
map.get("$defs")
.cloned()
.or_else(|| map.get("definitions").cloned())
} else {
None
};
if let Some(defs) = defs {
inline_refs_recursive(schema, &defs);
}
}
fn inline_refs_recursive(schema: &mut Value, defs: &Value) {
match schema {
Value::Object(map) => {
if let Some(Value::String(ref_path)) = map.get("$ref").cloned() {
let def_name = ref_path
.strip_prefix("#/$defs/")
.or_else(|| ref_path.strip_prefix("#/definitions/"));
if let Some(name) = def_name {
if let Some(def) = defs.get(name) {
*schema = def.clone();
inline_refs_recursive(schema, defs);
return;
}
}
}
for (_, v) in map.iter_mut() {
inline_refs_recursive(v, defs);
}
}
Value::Array(arr) => {
for item in arr.iter_mut() {
inline_refs_recursive(item, defs);
}
}
_ => {}
}
}
fn recurse_schema_fix(schema: &mut Value, fix_fn: fn(&mut Value)) {
if let Value::Object(map) = schema {
if let Some(Value::Object(props)) = map.get_mut("properties") {
for (_, prop_schema) in props.iter_mut() {
fix_fn(prop_schema);
}
}
if let Some(items) = map.get_mut("items") {
fix_fn(items);
}
if let Some(Value::Object(defs)) = map.get_mut("$defs") {
for (_, def_schema) in defs.iter_mut() {
fix_fn(def_schema);
}
}
if let Some(Value::Object(defs)) = map.get_mut("definitions") {
for (_, def_schema) in defs.iter_mut() {
fix_fn(def_schema);
}
}
for key in &["anyOf", "oneOf", "allOf"] {
if let Some(Value::Array(variants)) = map.get_mut(*key) {
for variant in variants.iter_mut() {
fix_fn(variant);
}
}
}
}
}
pub trait SchemaGenerator: Send + Sync {
fn generate_schema(&self, name: &str, description: &str, parameters: Value) -> Value;
}
pub struct AnthropicSchemaGenerator;
impl SchemaGenerator for AnthropicSchemaGenerator {
fn generate_schema(&self, name: &str, description: &str, parameters: Value) -> Value {
serde_json::json!({
"name": name,
"description": description,
"input_schema": parameters,
})
}
}
pub struct OpenAISchemaGenerator;
impl SchemaGenerator for OpenAISchemaGenerator {
fn generate_schema(&self, name: &str, description: &str, parameters: Value) -> Value {
serde_json::json!({
"type": "function",
"function": {
"name": name,
"description": description,
"parameters": parameters,
}
})
}
}
pub struct OpenAIResponsesSchemaGenerator;
impl SchemaGenerator for OpenAIResponsesSchemaGenerator {
fn generate_schema(&self, name: &str, description: &str, parameters: Value) -> Value {
serde_json::json!({
"type": "function",
"name": name,
"description": description,
"parameters": parameters,
})
}
}
pub struct GoogleSchemaGenerator;
impl SchemaGenerator for GoogleSchemaGenerator {
fn generate_schema(&self, name: &str, description: &str, parameters: Value) -> Value {
serde_json::json!({
"name": name,
"description": description,
"parameters": parameters,
})
}
}
pub fn get_schema_generator(provider: &str) -> Result<Box<dyn SchemaGenerator>> {
match provider {
"anthropic" => Ok(Box::new(AnthropicSchemaGenerator)),
"openai" => Ok(Box::new(OpenAISchemaGenerator)),
"openrouter" => Ok(Box::new(OpenAISchemaGenerator)), "custom-openai" => Ok(Box::new(OpenAISchemaGenerator)), "openai-responses" => Ok(Box::new(OpenAIResponsesSchemaGenerator)),
"openrouter-responses" => Ok(Box::new(OpenAIResponsesSchemaGenerator)),
"google" | "google_gemini" | "vertex_gemini" | "gemini" | "google-genai"
| "google-gemini" => Ok(Box::new(GoogleSchemaGenerator)),
"bedrock" => Ok(Box::new(AnthropicSchemaGenerator)), "google-anthropic" | "vertexai" => Ok(Box::new(AnthropicSchemaGenerator)), _ => Err(Error::NonRetryable(format!(
"Native tools not supported for provider: {}",
provider
))),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_anthropic_schema() {
let generator = AnthropicSchemaGenerator;
let params = serde_json::json!({
"type": "object",
"properties": {
"location": {"type": "string"}
}
});
let schema = generator.generate_schema("get_weather", "Get weather for location", params);
assert_eq!(schema["name"], "get_weather");
assert_eq!(schema["description"], "Get weather for location");
assert!(schema["input_schema"].is_object());
}
#[test]
fn test_openai_schema() {
let generator = OpenAISchemaGenerator;
let params = serde_json::json!({
"type": "object",
"properties": {
"location": {"type": "string"}
}
});
let schema = generator.generate_schema("get_weather", "Get weather for location", params);
assert_eq!(schema["type"], "function");
assert_eq!(schema["function"]["name"], "get_weather");
assert_eq!(
schema["function"]["description"],
"Get weather for location"
);
assert!(schema["function"]["parameters"].is_object());
}
#[test]
fn test_google_schema() {
let generator = GoogleSchemaGenerator;
let params = serde_json::json!({
"type": "object",
"properties": {
"location": {"type": "string"}
}
});
let schema = generator.generate_schema("get_weather", "Get weather for location", params);
assert_eq!(schema["name"], "get_weather");
assert_eq!(schema["description"], "Get weather for location");
assert!(schema["parameters"].is_object());
}
#[test]
fn test_get_schema_generator_anthropic() {
let result = get_schema_generator("anthropic");
assert!(result.is_ok());
}
#[test]
fn test_get_schema_generator_openai() {
let result = get_schema_generator("openai");
assert!(result.is_ok());
}
#[test]
fn test_get_schema_generator_openrouter() {
let result = get_schema_generator("openrouter");
assert!(result.is_ok());
}
#[test]
fn test_get_schema_generator_openai_responses() {
let result = get_schema_generator("openai-responses");
assert!(result.is_ok());
}
#[test]
fn test_get_schema_generator_openrouter_responses() {
let result = get_schema_generator("openrouter-responses");
assert!(result.is_ok());
}
#[test]
fn test_get_schema_generator_google() {
let result = get_schema_generator("google_gemini");
assert!(result.is_ok());
}
#[test]
fn test_get_schema_generator_bedrock() {
let result = get_schema_generator("bedrock");
assert!(result.is_ok());
}
#[test]
fn test_get_schema_generator_unsupported() {
let result = get_schema_generator("unsupported");
assert!(result.is_err());
}
}