doum_cli/llm/
parser.rs

1use crate::system::error::{DoumError, DoumResult};
2use serde::{Deserialize, Serialize};
3
4/// 모드 선택 응답
5#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct ModeSelectResponse {
7    pub mode: String,
8    pub reason: String,
9}
10
11/// Ask 모드 응답 (단순 문자열)
12pub type AskResponse = String;
13
14/// 명령 제안
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct CommandSuggestion {
17    pub cmd: String,
18    pub description: String,
19}
20
21/// Suggest 모드 응답
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct SuggestResponse {
24    pub suggestions: Vec<CommandSuggestion>,
25}
26
27/// Execute 모드 응답
28#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct ExecuteResponse {
30    pub command: String,
31    pub description: String,
32    pub is_dangerous: bool,
33}
34
35/// 모드 선택 응답 파싱
36pub fn parse_mode_select(json_str: &str) -> DoumResult<ModeSelectResponse> {
37    // JSON 추출 시도 (markdown 코드 블록 제거)
38    let cleaned = extract_json(json_str);
39
40    serde_json::from_str(&cleaned)
41        .map_err(|e| DoumError::Parse(format!("모드 선택 응답 파싱 실패: {}", e)))
42}
43
44/// Suggest 응답 파싱
45pub fn parse_suggest(json_str: &str) -> DoumResult<SuggestResponse> {
46    let cleaned = extract_json(json_str);
47
48    serde_json::from_str(&cleaned)
49        .map_err(|e| DoumError::Parse(format!("Suggest 응답 파싱 실패: {}", e)))
50}
51
52/// Execute 응답 파싱
53pub fn parse_execute(json_str: &str) -> DoumResult<ExecuteResponse> {
54    let cleaned = extract_json(json_str);
55
56    serde_json::from_str(&cleaned)
57        .map_err(|e| DoumError::Parse(format!("Execute 응답 파싱 실패: {}", e)))
58}
59
60/// JSON 추출 (markdown 코드 블록이나 불필요한 텍스트 제거)
61fn extract_json(text: &str) -> String {
62    let text = text.trim();
63
64    // ```json ... ``` 또는 ``` ... ``` 형식 처리
65    if let Some(start) = text.find("```")
66        && let Some(end) = text[start + 3..].find("```")
67    {
68        let json_block = &text[start + 3..start + 3 + end];
69        // ```json 같은 언어 태그 제거
70        let json_content = if let Some(newline) = json_block.find('\n') {
71            &json_block[newline + 1..]
72        } else {
73            json_block
74        };
75        return json_content.trim().to_string();
76    }
77
78    // { 로 시작하는 JSON 찾기
79    if let Some(start) = text.find('{')
80        && let Some(end) = text.rfind('}')
81        && end > start
82    {
83        return text[start..=end].to_string();
84    }
85
86    // 그대로 반환
87    text.to_string()
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93
94    #[test]
95    fn test_parse_mode_select() {
96        let json = r#"{"mode":"ask","reason":"사용자가 질문을 했습니다"}"#;
97        let result = parse_mode_select(json).unwrap();
98        assert_eq!(result.mode, "ask");
99        assert_eq!(result.reason, "사용자가 질문을 했습니다");
100    }
101
102    #[test]
103    fn test_parse_mode_select_with_markdown() {
104        let json = r#"
105```json
106{"mode":"execute","reason":"실행 요청"}
107```
108        "#;
109        let result = parse_mode_select(json).unwrap();
110        assert_eq!(result.mode, "execute");
111    }
112
113    #[test]
114    fn test_parse_suggest() {
115        let json = r#"
116{
117  "suggestions": [
118    {"cmd": "ls -la", "description": "모든 파일 나열"},
119    {"cmd": "dir", "description": "디렉터리 내용 보기"}
120  ]
121}
122        "#;
123        let result = parse_suggest(json).unwrap();
124        assert_eq!(result.suggestions.len(), 2);
125        assert_eq!(result.suggestions[0].cmd, "ls -la");
126    }
127
128    #[test]
129    fn test_parse_execute() {
130        let json = r#"
131{
132  "command": "echo hello",
133  "description": "hello 출력",
134  "is_dangerous": false
135}
136        "#;
137        let result = parse_execute(json).unwrap();
138        assert_eq!(result.command, "echo hello");
139        assert!(!result.is_dangerous);
140    }
141
142    #[test]
143    fn test_parse_execute_dangerous() {
144        let json = r#"{"command":"rm -rf /","description":"위험","is_dangerous":true}"#;
145        let result = parse_execute(json).unwrap();
146        assert!(result.is_dangerous);
147    }
148
149    #[test]
150    fn test_extract_json() {
151        let text = "Some text before\n{\"key\":\"value\"}\nSome text after";
152        let result = extract_json(text);
153        assert_eq!(result, r#"{"key":"value"}"#);
154    }
155
156    #[test]
157    fn test_extract_json_with_code_block() {
158        let text = "```json\n{\"key\":\"value\"}\n```";
159        let result = extract_json(text);
160        assert_eq!(result, r#"{"key":"value"}"#);
161    }
162}