kana 1.1.0

Quantum mechanics simulation — state vectors, operators, entanglement, quantum circuits
Documentation
//! AI integration — daimon/hoosh client for kana.

use serde::{Deserialize, Serialize};

use crate::error::{KanaError, Result};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DaimonConfig {
    pub endpoint: String,
    pub api_key: Option<String>,
}

impl Default for DaimonConfig {
    fn default() -> Self {
        Self {
            endpoint: "http://localhost:8090".into(),
            api_key: None,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HooshConfig {
    pub endpoint: String,
}

impl Default for HooshConfig {
    fn default() -> Self {
        Self {
            endpoint: "http://localhost:8088".into(),
        }
    }
}

pub struct DaimonClient {
    config: DaimonConfig,
    client: reqwest::Client,
}

impl DaimonClient {
    pub fn new(config: DaimonConfig) -> Result<Self> {
        let client = reqwest::Client::builder()
            .timeout(std::time::Duration::from_secs(30))
            .build()
            .map_err(|e| KanaError::InvalidParameter {
                reason: format!("failed to build HTTP client: {e}"),
            })?;
        Ok(Self { config, client })
    }

    pub async fn register_agent(&self) -> Result<String> {
        let body = serde_json::json!({
            "name": "kana",
            "capabilities": ["quantum_state", "operators", "entanglement", "circuits"],
        });
        let mut req = self
            .client
            .post(format!("{}/v1/agents/register", self.config.endpoint))
            .json(&body);
        if let Some(ref key) = self.config.api_key {
            req = req.header("Authorization", format!("Bearer {key}"));
        }
        let resp = req.send().await.map_err(|e| KanaError::InvalidParameter {
            reason: format!("registration request failed: {e}"),
        })?;
        if !resp.status().is_success() {
            return Err(KanaError::InvalidParameter {
                reason: format!("registration failed with status {}", resp.status()),
            });
        }
        let data: serde_json::Value =
            resp.json().await.map_err(|e| KanaError::InvalidParameter {
                reason: format!("invalid registration response: {e}"),
            })?;
        data["agent_id"]
            .as_str()
            .map(String::from)
            .ok_or_else(|| KanaError::InvalidParameter {
                reason: "registration response missing 'agent_id' field".into(),
            })
    }
}

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

    #[test]
    fn test_default_config() {
        let c = DaimonConfig::default();
        assert_eq!(c.endpoint, "http://localhost:8090");
        assert!(c.api_key.is_none());
    }

    #[test]
    fn test_hoosh_default() {
        let c = HooshConfig::default();
        assert_eq!(c.endpoint, "http://localhost:8088");
    }

    #[test]
    fn test_daimon_client_new() {
        let config = DaimonConfig::default();
        let client = DaimonClient::new(config);
        assert!(client.is_ok());
    }

    #[test]
    fn test_daimon_config_with_api_key() {
        let c = DaimonConfig {
            endpoint: "https://custom.host:9090".into(),
            api_key: Some("test-key".into()),
        };
        assert_eq!(c.api_key.as_deref(), Some("test-key"));
    }

    #[test]
    fn test_daimon_config_serde_roundtrip() {
        let c = DaimonConfig {
            endpoint: "http://example.com".into(),
            api_key: Some("key123".into()),
        };
        let json = serde_json::to_string(&c).unwrap();
        let back: DaimonConfig = serde_json::from_str(&json).unwrap();
        assert_eq!(back.endpoint, c.endpoint);
        assert_eq!(back.api_key, c.api_key);
    }

    #[test]
    fn test_hoosh_config_serde_roundtrip() {
        let c = HooshConfig::default();
        let json = serde_json::to_string(&c).unwrap();
        let back: HooshConfig = serde_json::from_str(&json).unwrap();
        assert_eq!(back.endpoint, c.endpoint);
    }
}