cc-switch-tui 0.2.1

All-in-One Assistant for Claude Code, Codex, Gemini, OpenCode, OpenClaw & Hermes
Documentation
use serde_json::Value;
use std::collections::HashSet;

#[cfg(test)]
pub fn filter_private_params(body: Value) -> Value {
    filter_private_params_with_whitelist(body, &[])
}

pub fn filter_private_params_with_whitelist(body: Value, whitelist: &[String]) -> Value {
    let whitelist_set: HashSet<&str> = whitelist.iter().map(|item| item.as_str()).collect();
    filter_recursive_with_whitelist(body, &mut Vec::new(), &whitelist_set)
}

fn filter_recursive_with_whitelist(
    value: Value,
    removed_keys: &mut Vec<String>,
    whitelist: &HashSet<&str>,
) -> Value {
    match value {
        Value::Object(map) => {
            let filtered = map
                .into_iter()
                .filter_map(|(key, value)| {
                    if key.starts_with('_') && !whitelist.contains(key.as_str()) {
                        removed_keys.push(key);
                        None
                    } else {
                        Some((
                            key,
                            filter_recursive_with_whitelist(value, removed_keys, whitelist),
                        ))
                    }
                })
                .collect();

            if !removed_keys.is_empty() {
                log::debug!("[BodyFilter] filtered private params: {removed_keys:?}");
                removed_keys.clear();
            }

            Value::Object(filtered)
        }
        Value::Array(values) => Value::Array(
            values
                .into_iter()
                .map(|value| filter_recursive_with_whitelist(value, removed_keys, whitelist))
                .collect(),
        ),
        other => other,
    }
}

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

    #[test]
    fn filters_private_params_recursively() {
        let input = json!({
            "model": "claude-3-5-sonnet",
            "_top_secret": true,
            "messages": [{
                "role": "user",
                "_message_secret": true,
                "content": [{
                    "type": "text",
                    "text": "hello",
                    "_content_secret": true
                }]
            }],
            "metadata": {
                "keep": "ok",
                "_trace_id": "drop"
            }
        });

        let output = filter_private_params(input);

        assert!(output.get("_top_secret").is_none());
        assert!(output.pointer("/messages/0/_message_secret").is_none());
        assert!(output
            .pointer("/messages/0/content/0/_content_secret")
            .is_none());
        assert!(output.pointer("/metadata/_trace_id").is_none());
        assert_eq!(
            output
                .pointer("/metadata/keep")
                .and_then(|value| value.as_str()),
            Some("ok")
        );
    }
}