va-ai-api-bridge 0.1.7

Protocol translation primitives for AI API request and response shapes.
Documentation
use serde_json::{json, Value};

#[derive(Debug, Clone)]
pub struct ZaiBridgeAdapter {
    thinking_enabled: bool,
}

impl ZaiBridgeAdapter {
    pub fn new(thinking_enabled: Option<bool>) -> Self {
        let thinking_enabled = thinking_enabled.unwrap_or(true);
        Self { thinking_enabled }
    }

    pub fn prepare_chat_request(&mut self, original_request: &Value, chat_request: &mut Value) {
        let Some(object) = chat_request.as_object_mut() else {
            return;
        };

        object.remove("reasoning");
        object.remove("reasoning_effort");
        object.remove("reasoningEffort");

        let thinking_enabled =
            thinking_from_original_request(original_request).unwrap_or(self.thinking_enabled);
        if !thinking_enabled {
            object.insert("thinking".to_string(), json!({ "type": "disabled" }));
        }
    }
}

fn thinking_from_original_request(request: &Value) -> Option<bool> {
    request
        .get("reasoning")
        .and_then(|reasoning| {
            reasoning
                .get("effort")
                .or_else(|| reasoning.get("reasoning_effort"))
                .or_else(|| reasoning.get("reasoningEffort"))
                .and_then(Value::as_str)
        })
        .or_else(|| request.get("reasoning_effort").and_then(Value::as_str))
        .or_else(|| request.get("reasoningEffort").and_then(Value::as_str))
        .map(reasoning_effort_enabled)
}

fn reasoning_effort_enabled(value: &str) -> bool {
    !matches!(
        value.trim().to_ascii_lowercase().as_str(),
        "off" | "none" | "disabled" | "disable" | "false"
    )
}

#[cfg(test)]
mod tests {
    use serde_json::json;

    use super::*;

    #[test]
    fn disables_zai_thinking_when_reasoning_is_off() {
        let mut adapter = ZaiBridgeAdapter::new(None);
        let mut chat_request = json!({ "model": "glm-5.1", "messages": [] });

        adapter.prepare_chat_request(
            &json!({ "reasoning": { "effort": "none" } }),
            &mut chat_request,
        );

        assert_eq!(chat_request["thinking"], json!({ "type": "disabled" }));
    }

    #[test]
    fn leaves_zai_default_thinking_unpatched_when_enabled() {
        let mut adapter = ZaiBridgeAdapter::new(None);
        let mut chat_request = json!({ "model": "glm-5.1", "messages": [] });

        adapter.prepare_chat_request(
            &json!({ "reasoning": { "effort": "high" } }),
            &mut chat_request,
        );

        assert!(chat_request.get("thinking").is_none());
    }
}