gephyr 1.16.18

Gephyr is a headless local AI relay/proxy API handling OpenAI, Claude, and Gemini-compatible APIs
Documentation
use serde_json::Value;

const STRIP_KEYS: &[&str] = &[
    "$schema",
    "$id",
    "title",
    "description",
    "default",
    "examples",
    "deprecated",
    "readOnly",
    "writeOnly",
    "additionalProperties",
    "unevaluatedProperties",
    "propertyNames",
    "minimum",
    "maximum",
    "exclusiveMinimum",
    "exclusiveMaximum",
    "multipleOf",
    "minLength",
    "maxLength",
    "pattern",
    "format",
    "minItems",
    "maxItems",
    "uniqueItems",
    "minProperties",
    "maxProperties",
    "contentEncoding",
    "contentMediaType",
    "contentSchema",
];

pub fn clean_json_schema(value: &mut Value) {
    match value {
        Value::Object(map) => {
            for key in STRIP_KEYS {
                map.remove(*key);
            }

            if let Some(ty) = map.get_mut("type") {
                match ty {
                    Value::String(s) => {
                        *s = s.to_ascii_lowercase();
                    }
                    Value::Array(items) => {
                        if let Some(non_null) = items.iter().find_map(|v| {
                            v.as_str()
                                .map(|s| s.to_ascii_lowercase())
                                .filter(|s| s != "null")
                        }) {
                            *ty = Value::String(non_null);
                        }
                    }
                    _ => {}
                }
            }

            for (_, child) in map.iter_mut() {
                clean_json_schema(child);
            }
        }
        Value::Array(items) => {
            for child in items.iter_mut() {
                clean_json_schema(child);
            }
        }
        _ => {}
    }
}

pub fn fix_tool_call_args(args: &mut Value, schema: &Value) {
    if let Value::String(s) = args {
        if let Ok(parsed) = serde_json::from_str::<Value>(s) {
            *args = parsed;
        }
    }

    coerce_by_schema(args, schema);
}

fn schema_type(schema: &Value) -> Option<&str> {
    schema.get("type").and_then(Value::as_str)
}

fn coerce_by_schema(value: &mut Value, schema: &Value) {
    match schema_type(schema) {
        Some("boolean") => {
            if let Value::String(s) = value {
                let lowered = s.trim().to_ascii_lowercase();
                if matches!(lowered.as_str(), "true" | "1" | "yes" | "on") {
                    *value = Value::Bool(true);
                } else if matches!(lowered.as_str(), "false" | "0" | "no" | "off") {
                    *value = Value::Bool(false);
                }
            }
        }
        Some("integer") => match value {
            Value::String(s) => {
                if let Ok(i) = s.trim().parse::<i64>() {
                    *value = Value::Number(i.into());
                }
            }
            Value::Number(n) => {
                if let Some(f) = n.as_f64() {
                    *value = Value::Number((f as i64).into());
                }
            }
            _ => {}
        },
        Some("number") => {
            if let Value::String(s) = value {
                if let Ok(f) = s.trim().parse::<f64>() {
                    if let Some(n) = serde_json::Number::from_f64(f) {
                        *value = Value::Number(n);
                    }
                }
            }
        }
        Some("string") => {
            if !value.is_string() {
                *value = Value::String(value.to_string());
            }
        }
        Some("array") => {
            if !value.is_array() {
                let single = value.take();
                *value = Value::Array(vec![single]);
            }
            if let (Some(item_schema), Value::Array(items)) = (schema.get("items"), value) {
                for item in items.iter_mut() {
                    coerce_by_schema(item, item_schema);
                }
            }
        }
        Some("object") => {
            if !value.is_object() {
                *value = Value::Object(serde_json::Map::new());
            }

            if let Value::Object(obj) = value {
                if let Some(Value::Object(props)) = schema.get("properties") {
                    for (k, child_schema) in props {
                        if let Some(v) = obj.get_mut(k) {
                            coerce_by_schema(v, child_schema);
                        }
                    }
                }
            }
        }
        _ => {}
    }
}