Parse Codex app-server patch payloads

This commit is contained in:
iris committed 2026-09-09 21:30:15 -04:00
1 parent 10ce1a216b
commit b00e89795e
1 file changed
+52 -17
+52 -17
View File
@@ -305,13 +305,47 @@ fn shell_word(word: &Value) -> String {
} }
fn file_change_diff(item: &Value) -> String { fn file_change_diff(item: &Value) -> String {
let Some(changes) = item.get("changes").and_then(Value::as_object) else { match item.get("changes") {
return String::new(); // Current app-server protocol: [{path, kind: {type, move_path?}, diff}].
}; Some(Value::Array(changes)) => changes
changes .iter()
.filter_map(|change| {
let path = change.get("path")?.as_str()?;
let kind = change.pointer("/kind/type").and_then(Value::as_str);
let moved = change.pointer("/kind/move_path").and_then(Value::as_str);
let body = change
.get("diff")
.and_then(Value::as_str)
.unwrap_or_default();
Some(render_file_diff(path, kind, moved, body))
})
.collect::<Vec<_>>()
.join("\n"),
// Older exec protocol and durable Codex rollouts: {path: {type, unified_diff, ...}}.
Some(Value::Object(changes)) => changes
.iter() .iter()
.map(|(path, change)| { .map(|(path, change)| {
let kind = change.get("type").and_then(Value::as_str); render_file_diff(
path,
change.get("type").and_then(Value::as_str),
change.get("move_path").and_then(Value::as_str),
change
.get("unified_diff")
.and_then(Value::as_str)
.unwrap_or_default(),
)
})
.collect::<Vec<_>>()
.join("\n"),
_ => String::new(),
}
}
fn render_file_diff(path: &str, kind: Option<&str>, moved: Option<&str>, body: &str) -> String {
// Some protocol revisions carry complete unified diffs and some carry only their hunks.
if body.starts_with("--- ") || body.starts_with("diff --git ") {
return body.to_string();
}
let from = if kind == Some("add") { let from = if kind == Some("add") {
"/dev/null" "/dev/null"
} else { } else {
@@ -320,19 +354,9 @@ fn file_change_diff(item: &Value) -> String {
let to = if kind == Some("delete") { let to = if kind == Some("delete") {
"/dev/null" "/dev/null"
} else { } else {
change moved.unwrap_or(path)
.get("move_path")
.and_then(Value::as_str)
.unwrap_or(path)
}; };
let body = change
.get("unified_diff")
.and_then(Value::as_str)
.unwrap_or_default();
format!("--- {from}\n+++ {to}\n{body}") format!("--- {from}\n+++ {to}\n{body}")
})
.collect::<Vec<_>>()
.join("\n")
} }
fn tool_output(item: &Value) -> String { fn tool_output(item: &Value) -> String {
@@ -537,7 +561,7 @@ mod tests {
assert!(started.is_empty()); assert!(started.is_empty());
assert_eq!( assert_eq!(
translator.translate(&line( translator.translate(&line(
r#"{"method":"item/completed","params":{"item":{"id":"patch-1","type":"fileChange","changes":{"src/main.rs":{"type":"update","unified_diff":"@@ -1 +1 @@\n-old\n+new\n","move_path":null}},"status":"completed","stdout":"Success"}}}"# r#"{"method":"item/completed","params":{"item":{"id":"patch-1","type":"fileChange","changes":[{"path":"src/main.rs","kind":{"type":"update","move_path":null},"diff":"@@ -1 +1 @@\n-old\n+new\n"}],"status":"completed","stdout":"Success"}}}"#
)), )),
vec![ vec![
patch_start( patch_start(
@@ -552,6 +576,17 @@ mod tests {
); );
} }
#[test]
fn the_legacy_file_change_shape_remains_readable() {
let item = line(
r#"{"changes":{"old.rs":{"type":"update","unified_diff":"@@ -1 +1 @@\n-a\n+b\n","move_path":"new.rs"}}}"#,
);
assert_eq!(
file_change_diff(&item),
"--- old.rs\n+++ new.rs\n@@ -1 +1 @@\n-a\n+b\n"
);
}
#[test] #[test]
fn translates_native_app_server_streaming_without_repeating_the_final_item() { fn translates_native_app_server_streaming_without_repeating_the_final_item() {
let mut translator = Translator::default(); let mut translator = Translator::default();