Skip to main content

ralph_workflow/agents/
parser.rs

1//! JSON parser type definitions for agent output parsing.
2//!
3//! This module defines the `JsonParserType` enum that determines how
4//! each agent's output stream is parsed. Different agents use different
5//! JSON streaming formats (Claude's stream-json, Codex's format, etc.).
6
7use serde::Deserialize;
8
9/// JSON parser type for agent output.
10///
11/// Different AI coding agents output their streaming JSON in different formats.
12/// This enum determines which parser to use for a given agent's output.
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Deserialize)]
14#[serde(rename_all = "lowercase")]
15pub enum JsonParserType {
16    /// Claude's stream-json format (also used by Qwen Code, CCS, and other compatible CLIs).
17    #[default]
18    Claude,
19    /// Codex's JSON format.
20    Codex,
21    /// Gemini's stream-json format.
22    Gemini,
23    /// `OpenCode`'s NDJSON format.
24    OpenCode,
25    /// Generic line-based output (no parsing, pass-through).
26    Generic,
27}
28
29impl JsonParserType {
30    /// Parse parser type from string.
31    ///
32    /// Supports common names: "claude", "ccs", "codex", "gemini", "opencode", "generic", "none", "raw".
33    /// CCS (Claude Code Switch) wraps Claude Code, so "ccs" maps to the Claude parser.
34    /// Unknown strings default to `Generic`.
35    #[must_use]
36    pub fn parse(s: &str) -> Self {
37        match s.to_lowercase().as_str() {
38            // CCS wraps Claude Code, so it uses the same stream-json format
39            "claude" | "ccs" => Self::Claude,
40            "codex" => Self::Codex,
41            "gemini" => Self::Gemini,
42            "opencode" => Self::OpenCode,
43            _ => Self::Generic,
44        }
45    }
46}
47
48impl std::fmt::Display for JsonParserType {
49    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50        match self {
51            Self::Claude => write!(f, "claude"),
52            Self::Codex => write!(f, "codex"),
53            Self::Gemini => write!(f, "gemini"),
54            Self::OpenCode => write!(f, "opencode"),
55            Self::Generic => write!(f, "generic"),
56        }
57    }
58}
59
60#[cfg(test)]
61mod tests {
62    use super::*;
63
64    #[test]
65    fn test_json_parser_type_parse() {
66        assert_eq!(JsonParserType::parse("claude"), JsonParserType::Claude);
67        assert_eq!(JsonParserType::parse("CLAUDE"), JsonParserType::Claude);
68        // CCS wraps Claude Code, so it uses the same parser
69        assert_eq!(JsonParserType::parse("ccs"), JsonParserType::Claude);
70        assert_eq!(JsonParserType::parse("CCS"), JsonParserType::Claude);
71        assert_eq!(JsonParserType::parse("codex"), JsonParserType::Codex);
72        assert_eq!(JsonParserType::parse("gemini"), JsonParserType::Gemini);
73        assert_eq!(JsonParserType::parse("GEMINI"), JsonParserType::Gemini);
74        assert_eq!(JsonParserType::parse("opencode"), JsonParserType::OpenCode);
75        assert_eq!(JsonParserType::parse("OPENCODE"), JsonParserType::OpenCode);
76        assert_eq!(JsonParserType::parse("generic"), JsonParserType::Generic);
77        assert_eq!(JsonParserType::parse("none"), JsonParserType::Generic);
78        assert_eq!(JsonParserType::parse("raw"), JsonParserType::Generic);
79        assert_eq!(JsonParserType::parse("unknown"), JsonParserType::Generic);
80    }
81
82    #[test]
83    fn test_json_parser_type_display() {
84        assert_eq!(format!("{}", JsonParserType::Claude), "claude");
85        assert_eq!(format!("{}", JsonParserType::Codex), "codex");
86        assert_eq!(format!("{}", JsonParserType::Gemini), "gemini");
87        assert_eq!(format!("{}", JsonParserType::OpenCode), "opencode");
88        assert_eq!(format!("{}", JsonParserType::Generic), "generic");
89    }
90}