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    pub fn parse(s: &str) -> Self {
36        match s.to_lowercase().as_str() {
37            // CCS wraps Claude Code, so it uses the same stream-json format
38            "claude" | "ccs" => Self::Claude,
39            "codex" => Self::Codex,
40            "gemini" => Self::Gemini,
41            "opencode" => Self::OpenCode,
42            _ => Self::Generic,
43        }
44    }
45}
46
47impl std::fmt::Display for JsonParserType {
48    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49        match self {
50            Self::Claude => write!(f, "claude"),
51            Self::Codex => write!(f, "codex"),
52            Self::Gemini => write!(f, "gemini"),
53            Self::OpenCode => write!(f, "opencode"),
54            Self::Generic => write!(f, "generic"),
55        }
56    }
57}
58
59#[cfg(test)]
60mod tests {
61    use super::*;
62
63    #[test]
64    fn test_json_parser_type_parse() {
65        assert_eq!(JsonParserType::parse("claude"), JsonParserType::Claude);
66        assert_eq!(JsonParserType::parse("CLAUDE"), JsonParserType::Claude);
67        // CCS wraps Claude Code, so it uses the same parser
68        assert_eq!(JsonParserType::parse("ccs"), JsonParserType::Claude);
69        assert_eq!(JsonParserType::parse("CCS"), JsonParserType::Claude);
70        assert_eq!(JsonParserType::parse("codex"), JsonParserType::Codex);
71        assert_eq!(JsonParserType::parse("gemini"), JsonParserType::Gemini);
72        assert_eq!(JsonParserType::parse("GEMINI"), JsonParserType::Gemini);
73        assert_eq!(JsonParserType::parse("opencode"), JsonParserType::OpenCode);
74        assert_eq!(JsonParserType::parse("OPENCODE"), JsonParserType::OpenCode);
75        assert_eq!(JsonParserType::parse("generic"), JsonParserType::Generic);
76        assert_eq!(JsonParserType::parse("none"), JsonParserType::Generic);
77        assert_eq!(JsonParserType::parse("raw"), JsonParserType::Generic);
78        assert_eq!(JsonParserType::parse("unknown"), JsonParserType::Generic);
79    }
80
81    #[test]
82    fn test_json_parser_type_display() {
83        assert_eq!(format!("{}", JsonParserType::Claude), "claude");
84        assert_eq!(format!("{}", JsonParserType::Codex), "codex");
85        assert_eq!(format!("{}", JsonParserType::Gemini), "gemini");
86        assert_eq!(format!("{}", JsonParserType::OpenCode), "opencode");
87        assert_eq!(format!("{}", JsonParserType::Generic), "generic");
88    }
89}