doum_cli/llm/
parser.rs

1use anyhow::{Context, Result};
2use serde::{Deserialize, Serialize};
3
4/// Select Mode Response
5#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct ModeSelectResponse {
7    pub mode: String,
8    pub reason: String,
9}
10
11/// Ask Mode Response
12pub type AskResponse = String;
13
14/// Command Suggestion
15#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
16pub struct CommandSuggestion {
17    pub cmd: String,
18    pub description: String,
19}
20
21/// Suggest Mode Response
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct SuggestResponse {
24    pub suggestions: Vec<CommandSuggestion>,
25}
26
27/// parse Mode Select response
28pub fn parse_mode_select(json_str: &str) -> Result<ModeSelectResponse> {
29    let cleaned = extract_json(json_str);
30
31    serde_json::from_str(&cleaned).context("Failed to parse Mode Select response")
32}
33
34/// parse Suggest response
35pub fn parse_suggest(json_str: &str) -> Result<SuggestResponse> {
36    let cleaned = extract_json(json_str);
37
38    serde_json::from_str(&cleaned).context("Failed to parse Suggest response")
39}
40
41/// Extract JSON content from text (handles code blocks and surrounding text)
42fn extract_json(text: &str) -> String {
43    let text = text.trim();
44
45    // ```json ... ``` or ``` ... ```
46    if let Some(start) = text.find("```")
47        && let Some(end) = text[start + 3..].find("```")
48    {
49        let json_block = &text[start + 3..start + 3 + end];
50        let json_content = if let Some(newline) = json_block.find('\n') {
51            &json_block[newline + 1..]
52        } else {
53            json_block
54        };
55        return json_content.trim().to_string();
56    }
57
58    // Find first { ... } block
59    if let Some(start) = text.find('{')
60        && let Some(end) = text.rfind('}')
61        && end > start
62    {
63        return text[start..=end].to_string();
64    }
65
66    // Return original text if no JSON found
67    text.to_string()
68}
69
70#[cfg(test)]
71mod tests {
72    use super::*;
73
74    #[test]
75    fn test_parse_mode_select() {
76        let json = r#"{"mode":"ask","reason":"사용자가 질문을 했습니다"}"#;
77        let result = parse_mode_select(json).unwrap();
78        assert_eq!(result.mode, "ask");
79        assert_eq!(result.reason, "사용자가 질문을 했습니다");
80    }
81
82    #[test]
83    fn test_parse_mode_select_with_markdown() {
84        let json = r#"
85```json
86{"mode":"execute","reason":"실행 요청"}
87```
88        "#;
89        let result = parse_mode_select(json).unwrap();
90        assert_eq!(result.mode, "execute");
91    }
92
93    #[test]
94    fn test_parse_suggest() {
95        let json = r#"
96{
97  "suggestions": [
98    {"cmd": "ls -la", "description": "모든 파일 나열"},
99    {"cmd": "dir", "description": "디렉터리 내용 보기"}
100  ]
101}
102        "#;
103        let result = parse_suggest(json).unwrap();
104        assert_eq!(result.suggestions.len(), 2);
105        assert_eq!(result.suggestions[0].cmd, "ls -la");
106    }
107
108    #[test]
109    fn test_extract_json() {
110        let text = "Some text before\n{\"key\":\"value\"}\nSome text after";
111        let result = extract_json(text);
112        assert_eq!(result, r#"{"key":"value"}"#);
113    }
114
115    #[test]
116    fn test_extract_json_with_code_block() {
117        let text = "```json\n{\"key\":\"value\"}\n```";
118        let result = extract_json(text);
119        assert_eq!(result, r#"{"key":"value"}"#);
120    }
121}