ironclaw 0.24.0

Secure personal AI assistant that protects your data and expands its capabilities on the fly
Documentation
//! OpenClaw configuration to IronClaw settings mapping.

use secrecy::SecretString;
use std::collections::HashMap;

use super::reader::OpenClawConfig;

/// Map OpenClaw configuration to IronClaw settings (dotted-key format).
pub fn map_openclaw_config_to_settings(
    config: &OpenClawConfig,
) -> HashMap<String, serde_json::Value> {
    let mut settings = HashMap::new();

    // Map LLM configuration
    if let Some(ref llm) = config.llm {
        if let Some(ref provider) = llm.provider {
            settings.insert(
                "llm.backend".to_string(),
                serde_json::Value::String(provider.clone()),
            );
        }

        if let Some(ref model) = llm.model {
            settings.insert(
                "llm.selected_model".to_string(),
                serde_json::Value::String(model.clone()),
            );
        }

        if let Some(ref base_url) = llm.base_url {
            settings.insert(
                "llm.base_url".to_string(),
                serde_json::Value::String(base_url.clone()),
            );
        }
    }

    // Map embeddings configuration
    if let Some(ref emb) = config.embeddings {
        if let Some(ref model) = emb.model {
            settings.insert(
                "embeddings.model".to_string(),
                serde_json::Value::String(model.clone()),
            );
        }

        if let Some(ref provider) = emb.provider {
            settings.insert(
                "embeddings.provider".to_string(),
                serde_json::Value::String(provider.clone()),
            );
        }
    }

    // Map any other top-level settings
    for (key, value) in &config.other_settings {
        // Safely pass through JSON-serializable values
        settings.insert(key.clone(), value.clone());
    }

    settings
}

/// Extract credentials from OpenClaw configuration.
///
/// Returns a list of (secret_name, secret_value) pairs that should be stored.
/// Secret values are never logged or printed.
pub fn extract_credentials(config: &OpenClawConfig) -> Vec<(String, SecretString)> {
    let mut credentials = Vec::new();

    // Extract LLM API key if present
    if let Some(ref llm) = config.llm
        && let Some(ref api_key) = llm.api_key
    {
        credentials.push(("llm_api_key".to_string(), api_key.clone()));
    }

    // Extract embeddings API key if present
    if let Some(ref emb) = config.embeddings
        && let Some(ref api_key) = emb.api_key
    {
        credentials.push(("embeddings_api_key".to_string(), api_key.clone()));
    }

    credentials
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::import::openclaw::reader::{OpenClawConfig, OpenClawLlmConfig};

    #[test]
    fn test_map_llm_config() {
        let mut config = OpenClawConfig {
            llm: None,
            embeddings: None,
            other_settings: HashMap::new(),
        };

        config.llm = Some(OpenClawLlmConfig {
            provider: Some("openai".to_string()),
            model: Some("gpt-4".to_string()),
            api_key: Some(SecretString::new("secret".to_string().into_boxed_str())),
            base_url: None,
        });

        let settings = map_openclaw_config_to_settings(&config);

        assert_eq!(
            settings.get("llm.backend"),
            Some(&serde_json::Value::String("openai".to_string()))
        );
        assert_eq!(
            settings.get("llm.selected_model"),
            Some(&serde_json::Value::String("gpt-4".to_string()))
        );
    }

    #[test]
    fn test_extract_credentials_never_logs() {
        let mut config = OpenClawConfig {
            llm: None,
            embeddings: None,
            other_settings: HashMap::new(),
        };

        config.llm = Some(OpenClawLlmConfig {
            provider: Some("anthropic".to_string()),
            model: Some("claude-3".to_string()),
            api_key: Some(SecretString::new(
                "secret-key-value".to_string().into_boxed_str(),
            )),
            base_url: None,
        });

        let creds = extract_credentials(&config);
        assert_eq!(creds.len(), 1);
        assert_eq!(creds[0].0, "llm_api_key");
        // Verify the value is wrapped in SecretString (never exposed in Debug output)
        assert!(!format!("{:?}", creds[0].1).contains("secret-key-value"));
    }
}