chimera-core 0.1.0

Shared traits, types, and transport for the chimera AI agent SDK
Documentation
use serde::{Deserialize, Serialize};
use strum::{Display, EnumString};

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
#[non_exhaustive]
pub enum Action {
    Command {
        command: String,
        #[serde(default)]
        output: String,
        exit_code: Option<i32>,
        status: ActionStatus,
    },
    FileChange {
        changes: Vec<FileChange>,
        status: ActionStatus,
    },
    McpToolCall {
        server: String,
        tool: String,
        #[serde(default)]
        arguments: serde_json::Value,
        result: Option<serde_json::Value>,
        error: Option<String>,
        status: ActionStatus,
    },
    WebSearch {
        query: String,
    },
    ToolUse {
        name: String,
        #[serde(default)]
        input: serde_json::Value,
        result: Option<serde_json::Value>,
        #[serde(default)]
        is_error: bool,
    },
    Collaboration {
        tool: String,
        #[serde(default)]
        agents: serde_json::Value,
        status: ActionStatus,
    },
    Other {
        name: String,
        #[serde(default)]
        data: serde_json::Value,
    },
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Display, EnumString)]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
#[non_exhaustive]
pub enum ActionStatus {
    InProgress,
    Completed,
    Failed,
    Declined,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FileChange {
    pub path: String,
    pub kind: FileChangeKind,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Display, EnumString)]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
#[non_exhaustive]
pub enum FileChangeKind {
    Add,
    Delete,
    Update,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Usage {
    #[serde(default)]
    pub input_tokens: Option<u64>,
    #[serde(default)]
    pub output_tokens: Option<u64>,
    #[serde(default)]
    pub cache_read_tokens: Option<u64>,
    #[serde(default)]
    pub cache_creation_tokens: Option<u64>,
    #[serde(default)]
    pub total_cost_usd: Option<f64>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct TurnResult {
    pub text: Option<String>,
    pub duration_ms: Option<u64>,
    pub session_id: Option<String>,
    pub num_turns: Option<u32>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PlanItem {
    pub text: String,
    pub completed: bool,
}

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

    #[test]
    fn action_status_display() {
        assert_eq!(ActionStatus::InProgress.to_string(), "in_progress");
        assert_eq!(ActionStatus::Completed.to_string(), "completed");
        assert_eq!(ActionStatus::Failed.to_string(), "failed");
        assert_eq!(ActionStatus::Declined.to_string(), "declined");
    }

    #[test]
    fn action_status_from_str() {
        assert_eq!(
            ActionStatus::from_str("in_progress").unwrap(),
            ActionStatus::InProgress
        );
        assert_eq!(
            ActionStatus::from_str("completed").unwrap(),
            ActionStatus::Completed
        );
    }

    #[test]
    fn file_change_kind_roundtrip() {
        assert_eq!(
            FileChangeKind::from_str("add").unwrap(),
            FileChangeKind::Add
        );
        assert_eq!(FileChangeKind::Delete.to_string(), "delete");
    }

    #[test]
    fn action_serde_roundtrip() {
        let action = Action::Command {
            command: "ls -la".into(),
            output: "total 0".into(),
            exit_code: Some(0),
            status: ActionStatus::Completed,
        };

        let json = serde_json::to_string(&action).unwrap();
        let parsed: Action = serde_json::from_str(&json).unwrap();

        match parsed {
            Action::Command {
                command,
                exit_code,
                status,
                ..
            } => {
                assert_eq!(command, "ls -la");
                assert_eq!(exit_code, Some(0));
                assert_eq!(status, ActionStatus::Completed);
            }
            _ => panic!("expected Command variant"),
        }
    }

    #[test]
    fn usage_defaults() {
        let usage: Usage = serde_json::from_str("{}").unwrap();
        assert!(usage.input_tokens.is_none());
        assert!(usage.output_tokens.is_none());
        assert!(usage.total_cost_usd.is_none());
    }
}