Skip to main content

aider/
stream_json.rs

1use std::{
2    ffi::OsString,
3    path::{Path, PathBuf},
4};
5
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8
9use crate::AiderCliError;
10
11#[derive(Debug, Clone, Eq, PartialEq)]
12pub struct AiderStreamJsonRunRequest {
13    prompt: String,
14    model: Option<String>,
15    working_dir: Option<PathBuf>,
16}
17
18impl AiderStreamJsonRunRequest {
19    pub fn new(prompt: impl Into<String>) -> Self {
20        Self {
21            prompt: prompt.into(),
22            model: None,
23            working_dir: None,
24        }
25    }
26
27    pub fn model(mut self, model: impl Into<String>) -> Self {
28        self.model = Some(model.into());
29        self
30    }
31
32    pub fn working_dir(mut self, path: impl Into<PathBuf>) -> Self {
33        self.working_dir = Some(path.into());
34        self
35    }
36
37    pub fn prompt(&self) -> &str {
38        &self.prompt
39    }
40
41    pub fn model_name(&self) -> Option<&str> {
42        self.model.as_deref()
43    }
44
45    pub fn working_directory(&self) -> Option<&Path> {
46        self.working_dir.as_deref()
47    }
48
49    pub(crate) fn argv(&self) -> Result<Vec<OsString>, AiderCliError> {
50        if self.prompt.trim().is_empty() {
51            return Err(AiderCliError::InvalidRequest(
52                "prompt must not be empty".to_string(),
53            ));
54        }
55
56        let mut argv = vec![
57            OsString::from("--prompt"),
58            OsString::from(self.prompt.as_str()),
59            OsString::from("--message-format"),
60            OsString::from("stream-json"),
61        ];
62
63        if let Some(model) = normalize_non_empty(self.model.as_deref()) {
64            argv.push(OsString::from("--model"));
65            argv.push(OsString::from(model));
66        }
67
68        Ok(argv)
69    }
70}
71
72fn normalize_non_empty(value: Option<&str>) -> Option<String> {
73    value
74        .map(str::trim)
75        .filter(|value| !value.is_empty())
76        .map(ToOwned::to_owned)
77}
78
79#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
80pub struct AiderToolResultError {
81    #[serde(default)]
82    pub r#type: Option<String>,
83    #[serde(default)]
84    pub message: Option<String>,
85}
86
87#[derive(Debug, Clone, Eq, PartialEq)]
88pub struct AiderStreamJsonResultPayload {
89    pub status: String,
90    pub error_type: Option<String>,
91    pub error_message: Option<String>,
92    pub stats: Option<Value>,
93    pub raw: Value,
94}
95
96#[derive(Debug, Clone, Eq, PartialEq)]
97pub enum AiderStreamJsonEvent {
98    Init {
99        session_id: String,
100        model: String,
101        raw: Value,
102    },
103    Message {
104        role: String,
105        content: String,
106        delta: bool,
107        raw: Value,
108    },
109    ToolUse {
110        tool_name: String,
111        tool_id: String,
112        parameters: Value,
113        raw: Value,
114    },
115    ToolResult {
116        tool_id: String,
117        status: String,
118        output: Option<String>,
119        error: Option<AiderToolResultError>,
120        raw: Value,
121    },
122    Error {
123        severity: String,
124        message: String,
125        raw: Value,
126    },
127    Result {
128        payload: AiderStreamJsonResultPayload,
129    },
130    Unknown {
131        event_type: String,
132        raw: Value,
133    },
134}
135
136impl AiderStreamJsonEvent {
137    pub fn raw(&self) -> &Value {
138        match self {
139            AiderStreamJsonEvent::Init { raw, .. } => raw,
140            AiderStreamJsonEvent::Message { raw, .. } => raw,
141            AiderStreamJsonEvent::ToolUse { raw, .. } => raw,
142            AiderStreamJsonEvent::ToolResult { raw, .. } => raw,
143            AiderStreamJsonEvent::Error { raw, .. } => raw,
144            AiderStreamJsonEvent::Result { payload } => &payload.raw,
145            AiderStreamJsonEvent::Unknown { raw, .. } => raw,
146        }
147    }
148
149    pub fn event_type(&self) -> &str {
150        match self {
151            AiderStreamJsonEvent::Init { .. } => "init",
152            AiderStreamJsonEvent::Message { .. } => "message",
153            AiderStreamJsonEvent::ToolUse { .. } => "tool_use",
154            AiderStreamJsonEvent::ToolResult { .. } => "tool_result",
155            AiderStreamJsonEvent::Error { .. } => "error",
156            AiderStreamJsonEvent::Result { .. } => "result",
157            AiderStreamJsonEvent::Unknown { event_type, .. } => event_type.as_str(),
158        }
159    }
160}
161
162#[derive(Debug, Clone, Copy, Eq, PartialEq)]
163pub enum AiderStreamJsonErrorCode {
164    JsonParse,
165    TypedParse,
166}
167
168#[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)]
169#[error("{message}")]
170pub struct AiderStreamJsonError {
171    pub code: AiderStreamJsonErrorCode,
172    pub message: String,
173    pub details: String,
174}
175
176impl AiderStreamJsonError {
177    fn new(code: AiderStreamJsonErrorCode, message: String) -> Self {
178        Self {
179            code,
180            details: message.clone(),
181            message,
182        }
183    }
184}
185
186#[derive(Debug, Clone, Default)]
187pub struct AiderStreamJsonParser;
188
189impl AiderStreamJsonParser {
190    pub fn new() -> Self {
191        Self
192    }
193
194    pub fn parse_line(
195        &mut self,
196        line: &str,
197    ) -> Result<Option<AiderStreamJsonEvent>, AiderStreamJsonError> {
198        let line = line.strip_suffix('\r').unwrap_or(line);
199        if line.chars().all(|ch| ch.is_whitespace()) {
200            return Ok(None);
201        }
202
203        let value: Value = serde_json::from_str(line).map_err(|err| {
204            AiderStreamJsonError::new(
205                AiderStreamJsonErrorCode::JsonParse,
206                format!("invalid JSON: {err}"),
207            )
208        })?;
209
210        self.parse_json(&value)
211    }
212
213    pub fn parse_json(
214        &mut self,
215        value: &Value,
216    ) -> Result<Option<AiderStreamJsonEvent>, AiderStreamJsonError> {
217        let obj = value.as_object().ok_or_else(|| {
218            AiderStreamJsonError::new(
219                AiderStreamJsonErrorCode::TypedParse,
220                "expected JSON object".to_string(),
221            )
222        })?;
223
224        let event_type = get_required_str(obj, "type").map_err(|message| {
225            AiderStreamJsonError::new(AiderStreamJsonErrorCode::TypedParse, message)
226        })?;
227
228        let event = match event_type.as_str() {
229            "init" => AiderStreamJsonEvent::Init {
230                session_id: get_required_str(obj, "session_id").map_err(typed_parse_error)?,
231                model: get_required_str(obj, "model").map_err(typed_parse_error)?,
232                raw: value.clone(),
233            },
234            "message" => AiderStreamJsonEvent::Message {
235                role: get_required_str(obj, "role").map_err(typed_parse_error)?,
236                content: get_required_str(obj, "content").map_err(typed_parse_error)?,
237                delta: obj.get("delta").and_then(Value::as_bool).unwrap_or(false),
238                raw: value.clone(),
239            },
240            "tool_use" => AiderStreamJsonEvent::ToolUse {
241                tool_name: get_required_str(obj, "tool_name").map_err(typed_parse_error)?,
242                tool_id: get_required_str(obj, "tool_id").map_err(typed_parse_error)?,
243                parameters: obj
244                    .get("parameters")
245                    .cloned()
246                    .ok_or_else(|| typed_parse_error("expected field `parameters`".to_string()))?,
247                raw: value.clone(),
248            },
249            "tool_result" => AiderStreamJsonEvent::ToolResult {
250                tool_id: get_required_str(obj, "tool_id").map_err(typed_parse_error)?,
251                status: get_required_str(obj, "status").map_err(typed_parse_error)?,
252                output: obj
253                    .get("output")
254                    .and_then(Value::as_str)
255                    .map(ToOwned::to_owned),
256                error: obj
257                    .get("error")
258                    .cloned()
259                    .map(serde_json::from_value)
260                    .transpose()
261                    .map_err(|_| typed_parse_error("expected object field `error`".to_string()))?,
262                raw: value.clone(),
263            },
264            "error" => AiderStreamJsonEvent::Error {
265                severity: get_required_str(obj, "severity").map_err(typed_parse_error)?,
266                message: get_required_str(obj, "message").map_err(typed_parse_error)?,
267                raw: value.clone(),
268            },
269            "result" => AiderStreamJsonEvent::Result {
270                payload: AiderStreamJsonResultPayload {
271                    status: get_required_str(obj, "status").map_err(typed_parse_error)?,
272                    error_type: obj
273                        .get("error")
274                        .and_then(Value::as_object)
275                        .and_then(|error| error.get("type"))
276                        .and_then(Value::as_str)
277                        .map(ToOwned::to_owned),
278                    error_message: obj
279                        .get("error")
280                        .and_then(Value::as_object)
281                        .and_then(|error| error.get("message"))
282                        .and_then(Value::as_str)
283                        .map(ToOwned::to_owned),
284                    stats: obj.get("stats").cloned(),
285                    raw: value.clone(),
286                },
287            },
288            _ => AiderStreamJsonEvent::Unknown {
289                event_type,
290                raw: value.clone(),
291            },
292        };
293
294        Ok(Some(event))
295    }
296}
297
298fn typed_parse_error(message: String) -> AiderStreamJsonError {
299    AiderStreamJsonError::new(AiderStreamJsonErrorCode::TypedParse, message)
300}
301
302#[derive(Debug, Clone, Eq, PartialEq)]
303pub struct AiderStreamJsonLine {
304    pub line_number: usize,
305    pub raw: String,
306}
307
308#[derive(Debug, Clone, Eq, PartialEq)]
309pub enum AiderStreamJsonLineOutcome {
310    Ok {
311        line: AiderStreamJsonLine,
312        event: AiderStreamJsonEvent,
313    },
314    Err {
315        line: AiderStreamJsonLine,
316        error: AiderStreamJsonError,
317    },
318}
319
320pub fn parse_stream_json_lines(input: &str) -> Vec<AiderStreamJsonLineOutcome> {
321    let mut parser = AiderStreamJsonParser::new();
322    let mut outcomes = Vec::new();
323
324    for (index, raw) in input.lines().enumerate() {
325        let line = AiderStreamJsonLine {
326            line_number: index + 1,
327            raw: raw.to_string(),
328        };
329
330        match parser.parse_line(raw) {
331            Ok(Some(event)) => outcomes.push(AiderStreamJsonLineOutcome::Ok { line, event }),
332            Ok(None) => {}
333            Err(error) => outcomes.push(AiderStreamJsonLineOutcome::Err { line, error }),
334        }
335    }
336
337    outcomes
338}
339
340fn get_required_str(obj: &serde_json::Map<String, Value>, key: &str) -> Result<String, String> {
341    obj.get(key)
342        .and_then(Value::as_str)
343        .map(ToOwned::to_owned)
344        .ok_or_else(|| format!("expected string field `{key}`"))
345}