mirror of
https://github.com/openai/codex.git
synced 2026-06-01 19:02:59 +00:00
Wire the PatchUpdated events through app_server (#18289)
Wires patch_updated events through app_server. These events are parsed and streamed while apply_patch is being written by the model. Also adds 500ms of buffering to the patch_updated events in the diff_consumer. The eventual goal is to use this to display better progress indicators in the codex app.
This commit is contained in:
@@ -3,6 +3,8 @@ use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
|
||||
use crate::apply_patch;
|
||||
use crate::apply_patch::InternalApplyPatchInvocation;
|
||||
@@ -44,12 +46,16 @@ use codex_sandboxing::policy_transforms::normalize_additional_permissions;
|
||||
use codex_tools::ApplyPatchToolArgs;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
|
||||
const APPLY_PATCH_ARGUMENT_DIFF_BUFFER_INTERVAL: Duration = Duration::from_millis(500);
|
||||
|
||||
pub struct ApplyPatchHandler;
|
||||
|
||||
#[derive(Default)]
|
||||
struct ApplyPatchArgumentDiffConsumer {
|
||||
input: String,
|
||||
last_progress: Option<Vec<Hunk>>,
|
||||
last_sent_at: Option<Instant>,
|
||||
pending: Option<PatchApplyUpdatedEvent>,
|
||||
}
|
||||
|
||||
impl ToolArgumentDiffConsumer for ApplyPatchArgumentDiffConsumer {
|
||||
@@ -66,6 +72,11 @@ impl ToolArgumentDiffConsumer for ApplyPatchArgumentDiffConsumer {
|
||||
self.push_delta(call_id, diff)
|
||||
.map(EventMsg::PatchApplyUpdated)
|
||||
}
|
||||
|
||||
fn flush_on_complete(&mut self) -> Option<EventMsg> {
|
||||
self.flush_update_on_complete()
|
||||
.map(EventMsg::PatchApplyUpdated)
|
||||
}
|
||||
}
|
||||
|
||||
impl ApplyPatchArgumentDiffConsumer {
|
||||
@@ -82,7 +93,29 @@ impl ApplyPatchArgumentDiffConsumer {
|
||||
|
||||
let changes = convert_apply_patch_hunks_to_protocol(&hunks);
|
||||
self.last_progress = Some(hunks);
|
||||
Some(PatchApplyUpdatedEvent { call_id, changes })
|
||||
let event = PatchApplyUpdatedEvent { call_id, changes };
|
||||
let now = Instant::now();
|
||||
match self.last_sent_at {
|
||||
Some(last_sent_at)
|
||||
if now.duration_since(last_sent_at) < APPLY_PATCH_ARGUMENT_DIFF_BUFFER_INTERVAL =>
|
||||
{
|
||||
self.pending = Some(event);
|
||||
None
|
||||
}
|
||||
Some(_) | None => {
|
||||
self.pending = None;
|
||||
self.last_sent_at = Some(now);
|
||||
Some(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn flush_update_on_complete(&mut self) -> Option<PatchApplyUpdatedEvent> {
|
||||
let event = self.pending.take();
|
||||
if event.is_some() {
|
||||
self.last_sent_at = Some(Instant::now());
|
||||
}
|
||||
event
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -54,9 +54,13 @@ fn diff_consumer_streams_apply_patch_changes() {
|
||||
)
|
||||
);
|
||||
|
||||
let event = consumer
|
||||
.push_delta("call-1".to_string(), "\n+world")
|
||||
.expect("progress event");
|
||||
assert!(
|
||||
consumer
|
||||
.push_delta("call-1".to_string(), "\n+world")
|
||||
.is_none()
|
||||
);
|
||||
|
||||
let event = consumer.flush_update_on_complete().expect("progress event");
|
||||
assert_eq!(
|
||||
(event.call_id, event.changes),
|
||||
(
|
||||
@@ -71,6 +75,39 @@ fn diff_consumer_streams_apply_patch_changes() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn diff_consumer_sends_next_update_after_buffer_interval() {
|
||||
let mut consumer = ApplyPatchArgumentDiffConsumer::default();
|
||||
consumer.push_delta("call-1".to_string(), "*** Begin Patch\n");
|
||||
let first = consumer
|
||||
.push_delta("call-1".to_string(), "*** Add File: hello.txt\n+hello")
|
||||
.expect("first progress event");
|
||||
assert_eq!(
|
||||
first.changes,
|
||||
HashMap::from([(
|
||||
PathBuf::from("hello.txt"),
|
||||
FileChange::Add {
|
||||
content: "hello\n".to_string(),
|
||||
},
|
||||
)])
|
||||
);
|
||||
|
||||
consumer.last_sent_at =
|
||||
Some(std::time::Instant::now() - APPLY_PATCH_ARGUMENT_DIFF_BUFFER_INTERVAL);
|
||||
let second = consumer
|
||||
.push_delta("call-1".to_string(), "\n+world")
|
||||
.expect("second progress event");
|
||||
assert_eq!(
|
||||
second.changes,
|
||||
HashMap::from([(
|
||||
PathBuf::from("hello.txt"),
|
||||
FileChange::Add {
|
||||
content: "hello\nworld\n".to_string(),
|
||||
},
|
||||
)])
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn approval_keys_include_move_destination() {
|
||||
let tmp = TempDir::new().expect("tmp");
|
||||
|
||||
@@ -95,6 +95,11 @@ pub(crate) trait ToolArgumentDiffConsumer: Send {
|
||||
/// Consume the next argument diff for a tool call.
|
||||
fn consume_diff(&mut self, turn: &TurnContext, call_id: String, diff: &str)
|
||||
-> Option<EventMsg>;
|
||||
|
||||
/// Flush any buffered event before the tool call completes.
|
||||
fn flush_on_complete(&mut self) -> Option<EventMsg> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct AnyToolResult {
|
||||
|
||||
Reference in New Issue
Block a user