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
+63 -28
+63 -28
View File
@@ -305,34 +305,58 @@ 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()
.iter() .filter_map(|change| {
.map(|(path, change)| { let path = change.get("path")?.as_str()?;
let kind = change.get("type").and_then(Value::as_str); let kind = change.pointer("/kind/type").and_then(Value::as_str);
let from = if kind == Some("add") { let moved = change.pointer("/kind/move_path").and_then(Value::as_str);
"/dev/null" let body = change
} else { .get("diff")
path
};
let to = if kind == Some("delete") {
"/dev/null"
} else {
change
.get("move_path")
.and_then(Value::as_str) .and_then(Value::as_str)
.unwrap_or(path) .unwrap_or_default();
}; Some(render_file_diff(path, kind, moved, body))
let body = change })
.get("unified_diff") .collect::<Vec<_>>()
.and_then(Value::as_str) .join("\n"),
.unwrap_or_default(); // Older exec protocol and durable Codex rollouts: {path: {type, unified_diff, ...}}.
format!("--- {from}\n+++ {to}\n{body}") Some(Value::Object(changes)) => changes
}) .iter()
.collect::<Vec<_>>() .map(|(path, change)| {
.join("\n") 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") {
"/dev/null"
} else {
path
};
let to = if kind == Some("delete") {
"/dev/null"
} else {
moved.unwrap_or(path)
};
format!("--- {from}\n+++ {to}\n{body}")
} }
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();