Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions codex-rs/core/tests/common/responses.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,39 @@ pub fn ev_function_call(call_id: &str, name: &str, arguments: &str) -> Value {
})
}

/// Convenience: SSE event for an `apply_patch` custom tool call with raw patch
/// text. This mirrors the payload produced by the Responses API when the model
/// invokes `apply_patch` directly (before we convert it to a function call).
pub fn ev_apply_patch_custom_tool_call(call_id: &str, patch: &str) -> Value {
serde_json::json!({
"type": "response.output_item.done",
"item": {
"type": "custom_tool_call",
"name": "apply_patch",
"input": patch,
"call_id": call_id
}
})
}

/// Convenience: SSE event for an `apply_patch` function call. The Responses API
/// wraps the patch content in a JSON string under the `input` key; we recreate
/// the same structure so downstream code exercises the full parsing path.
pub fn ev_apply_patch_function_call(call_id: &str, patch: &str) -> Value {
let arguments = serde_json::json!({ "input": patch });
let arguments = serde_json::to_string(&arguments).expect("serialize apply_patch arguments");

serde_json::json!({
"type": "response.output_item.done",
"item": {
"type": "function_call",
"name": "apply_patch",
"arguments": arguments,
"call_id": call_id
}
})
}

pub fn sse_response(body: String) -> ResponseTemplate {
ResponseTemplate::new(200)
.insert_header("content-type", "text/event-stream")
Expand Down
25 changes: 0 additions & 25 deletions codex-rs/exec/tests/fixtures/sse_apply_patch_add.json

This file was deleted.

25 changes: 0 additions & 25 deletions codex-rs/exec/tests/fixtures/sse_apply_patch_freeform_add.json

This file was deleted.

25 changes: 0 additions & 25 deletions codex-rs/exec/tests/fixtures/sse_apply_patch_freeform_update.json

This file was deleted.

25 changes: 0 additions & 25 deletions codex-rs/exec/tests/fixtures/sse_apply_patch_update.json

This file was deleted.

16 changes: 0 additions & 16 deletions codex-rs/exec/tests/fixtures/sse_response_completed.json

This file was deleted.

71 changes: 52 additions & 19 deletions codex-rs/exec/tests/suite/apply_patch.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
#![allow(clippy::expect_used, clippy::unwrap_used)]
#![allow(clippy::expect_used, clippy::unwrap_used, unused_imports)]

use anyhow::Context;
use assert_cmd::prelude::*;
use codex_core::CODEX_APPLY_PATCH_ARG1;
use core_test_support::responses::ev_apply_patch_custom_tool_call;
use core_test_support::responses::ev_apply_patch_function_call;
use core_test_support::responses::ev_completed;
use core_test_support::responses::sse;
use std::fs;
use std::process::Command;
use tempfile::tempdir;
Expand Down Expand Up @@ -55,15 +59,28 @@ async fn test_apply_patch_tool() -> anyhow::Result<()> {

let tmp_cwd = tempdir().expect("failed to create temp dir");
let tmp_path = tmp_cwd.path().to_path_buf();
run_e2e_exec_test(
tmp_cwd.path(),
vec![
include_str!("../fixtures/sse_apply_patch_add.json").to_string(),
include_str!("../fixtures/sse_apply_patch_update.json").to_string(),
include_str!("../fixtures/sse_response_completed.json").to_string(),
],
)
.await;
let add_patch = r#"*** Begin Patch
*** Add File: test.md
+Hello world
*** End Patch"#;
let update_patch = r#"*** Begin Patch
*** Update File: test.md
@@
-Hello world
+Final text
*** End Patch"#;
let response_streams = vec![
sse(vec![
ev_apply_patch_custom_tool_call("request_0", add_patch),
ev_completed("request_0"),
]),
sse(vec![
ev_apply_patch_function_call("request_1", update_patch),
ev_completed("request_1"),
]),
sse(vec![ev_completed("request_2")]),
];
run_e2e_exec_test(tmp_cwd.path(), response_streams).await;

let final_path = tmp_path.join("test.md");
let contents = std::fs::read_to_string(&final_path)
Expand All @@ -86,15 +103,31 @@ async fn test_apply_patch_freeform_tool() -> anyhow::Result<()> {
}

let tmp_cwd = tempdir().expect("failed to create temp dir");
run_e2e_exec_test(
tmp_cwd.path(),
vec![
include_str!("../fixtures/sse_apply_patch_freeform_add.json").to_string(),
include_str!("../fixtures/sse_apply_patch_freeform_update.json").to_string(),
include_str!("../fixtures/sse_response_completed.json").to_string(),
],
)
.await;
let freeform_add_patch = r#"*** Begin Patch
*** Add File: app.py
+class BaseClass:
+ def method():
+ return False
*** End Patch"#;
let freeform_update_patch = r#"*** Begin Patch
*** Update File: app.py
@@ def method():
- return False
+
+ return True
*** End Patch"#;
let response_streams = vec![
sse(vec![
ev_apply_patch_custom_tool_call("request_0", freeform_add_patch),
ev_completed("request_0"),
]),
sse(vec![
ev_apply_patch_custom_tool_call("request_1", freeform_update_patch),
ev_completed("request_1"),
]),
sse(vec![ev_completed("request_2")]),
];
run_e2e_exec_test(tmp_cwd.path(), response_streams).await;

// Verify final file contents
let final_path = tmp_cwd.path().join("app.py");
Expand Down
6 changes: 1 addition & 5 deletions codex-rs/exec/tests/suite/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

use anyhow::Context;
use assert_cmd::prelude::*;
use core_test_support::load_sse_fixture_with_id_from_str;
use std::path::Path;
use std::process::Command;
use std::sync::atomic::AtomicUsize;
Expand All @@ -27,10 +26,7 @@ impl Respond for SeqResponder {
match self.responses.get(call_num) {
Some(body) => wiremock::ResponseTemplate::new(200)
.insert_header("content-type", "text/event-stream")
.set_body_raw(
load_sse_fixture_with_id_from_str(body, &format!("request_{call_num}")),
"text/event-stream",
),
.set_body_string(body.clone()),
None => panic!("no response for {call_num}"),
}
}
Expand Down
Loading