chimera-core 0.2.0

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

use crate::{Action, PlanItem, TurnResult, Usage};

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
#[non_exhaustive]
pub enum AgentEvent {
    // Session lifecycle
    SessionStarted {
        session_id: String,
    },
    TurnStarted,
    TurnCompleted {
        #[serde(default)]
        usage: Option<Usage>,
        #[serde(default)]
        result: Option<TurnResult>,
    },
    TurnFailed {
        message: String,
    },

    // Content
    Message {
        id: String,
        text: String,
    },
    TextDelta {
        id: String,
        delta: String,
    },
    Reasoning {
        id: String,
        text: String,
    },

    // Actions
    ActionStarted {
        id: String,
        action: Action,
    },
    ActionUpdated {
        id: String,
        action: Action,
    },
    ActionCompleted {
        id: String,
        action: Action,
    },

    // Progress
    PlanUpdated {
        items: Vec<PlanItem>,
    },

    // Tool / function calls
    ToolCall {
        /// Call ID assigned by the model (used for multi-call correlation).
        id: String,
        /// Name of the function the model chose to call.
        name: String,
        /// Raw JSON string of the function arguments.
        arguments: String,
    },

    // Errors
    Error {
        message: String,
    },

    // Pass-through
    Raw {
        backend: String,
        data: serde_json::Value,
    },
}

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

    #[test]
    fn session_started_serde() {
        let event = AgentEvent::SessionStarted {
            session_id: "abc-123".into(),
        };
        let json = serde_json::to_string(&event).unwrap();
        assert!(json.contains("\"type\":\"session_started\""));

        let parsed: AgentEvent = serde_json::from_str(&json).unwrap();
        match parsed {
            AgentEvent::SessionStarted { session_id } => assert_eq!(session_id, "abc-123"),
            _ => panic!("wrong variant"),
        }
    }

    #[test]
    fn turn_completed_with_usage() {
        let event = AgentEvent::TurnCompleted {
            usage: Some(Usage {
                input_tokens: Some(100),
                output_tokens: Some(50),
                ..Default::default()
            }),
            result: None,
        };
        let json = serde_json::to_string(&event).unwrap();
        let parsed: AgentEvent = serde_json::from_str(&json).unwrap();
        match parsed {
            AgentEvent::TurnCompleted { usage, .. } => {
                let u = usage.unwrap();
                assert_eq!(u.input_tokens, Some(100));
                assert_eq!(u.output_tokens, Some(50));
            }
            _ => panic!("wrong variant"),
        }
    }

    #[test]
    fn action_started_serde() {
        let event = AgentEvent::ActionStarted {
            id: "item-1".into(),
            action: Action::Command {
                command: "echo hi".into(),
                output: String::new(),
                exit_code: None,
                status: ActionStatus::InProgress,
            },
        };
        let json = serde_json::to_string(&event).unwrap();
        assert!(json.contains("\"type\":\"action_started\""));

        let parsed: AgentEvent = serde_json::from_str(&json).unwrap();
        match parsed {
            AgentEvent::ActionStarted { id, .. } => assert_eq!(id, "item-1"),
            _ => panic!("wrong variant"),
        }
    }

    #[test]
    fn raw_event_serde() {
        let event = AgentEvent::Raw {
            backend: "codex".into(),
            data: serde_json::json!({"custom": true}),
        };
        let json = serde_json::to_string(&event).unwrap();
        let parsed: AgentEvent = serde_json::from_str(&json).unwrap();
        match parsed {
            AgentEvent::Raw { backend, data } => {
                assert_eq!(backend, "codex");
                assert_eq!(data["custom"], true);
            }
            _ => panic!("wrong variant"),
        }
    }
}