mirror of
https://github.com/openai/codex.git
synced 2026-10-21 17:48:44 +00:00
Track code-mode cell lifecycle and result buffering time (#55653)
## What changed - Add `CodeModeCellTiming` notifications for yielded executions, terminal waits, and unavailable cells, with thread, call, and cell identifiers. - Measure how long completed results remain buffered before retrieval and carry `result_buffered_for` through the runtime, stdio protocol, and gRPC response metadata. Immediate delivery reports zero; waits without a completed result report no buffering measurement. - Default missing buffering data to zero for compatibility with older hosts. ## Testing Extend runtime tests to verify buffering duration with a paused clock and retrieval through termination. Add gRPC conversion tests for nanosecond precision, optional metadata, and the distinction between immediate delivery and no completed result. GitOrigin-RevId: 60c0c5a3affa69ac3779bf1812fca50f5748af76
This commit is contained in:
committed by
copyberry
parent
801bd4d773
commit
87f3c3284e
@@ -315,6 +315,7 @@ async fn run_cell<H: CellHost>(
|
||||
.await;
|
||||
let event = CellEvent::Completed {
|
||||
content_items: std::mem::take(&mut content_items),
|
||||
result_buffered_for: std::time::Duration::ZERO,
|
||||
error_text: Some("exec runtime ended unexpectedly".to_string()),
|
||||
};
|
||||
let rejected_event = match host
|
||||
@@ -471,6 +472,7 @@ async fn run_cell<H: CellHost>(
|
||||
.await;
|
||||
let event = CellEvent::Completed {
|
||||
content_items: std::mem::take(&mut content_items),
|
||||
result_buffered_for: std::time::Duration::ZERO,
|
||||
error_text,
|
||||
};
|
||||
let rejected_event = match host
|
||||
|
||||
@@ -208,6 +208,7 @@ async fn runtime_thread_panic_remains_a_cell_error_without_owner_supervision() {
|
||||
harness.initial_event_rx.await.expect("initial event"),
|
||||
Ok(CellEvent::Completed {
|
||||
content_items: Vec::new(),
|
||||
result_buffered_for: Duration::ZERO,
|
||||
error_text: Some("exec runtime ended unexpectedly".to_string()),
|
||||
})
|
||||
);
|
||||
@@ -469,11 +470,12 @@ async fn dropped_pending_observer_preserves_the_frontier_for_the_next_observatio
|
||||
harness.task.await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[tokio::test(start_paused = true)]
|
||||
async fn only_the_first_termination_claims_a_buffered_completion() {
|
||||
let cell_state = CellState::new(CancellationToken::new());
|
||||
let completion = CellEvent::Completed {
|
||||
content_items: Vec::new(),
|
||||
result_buffered_for: Duration::ZERO,
|
||||
error_text: None,
|
||||
};
|
||||
assert_eq!(
|
||||
@@ -510,6 +512,7 @@ async fn termination_claim_prevents_stored_value_commit() {
|
||||
let mut commit_ran = false;
|
||||
let completion = CellEvent::Completed {
|
||||
content_items: Vec::new(),
|
||||
result_buffered_for: Duration::ZERO,
|
||||
error_text: None,
|
||||
};
|
||||
|
||||
@@ -533,11 +536,12 @@ async fn termination_claim_prevents_stored_value_commit() {
|
||||
assert_eq!(termination.await, Ok(terminated));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn failed_completion_delivery_rebuffers_the_event() {
|
||||
#[tokio::test(start_paused = true)]
|
||||
async fn failed_completion_delivery_rebuffers_the_event() {
|
||||
let cell_state = CellState::new(CancellationToken::new());
|
||||
let event = CellEvent::Completed {
|
||||
content_items: Vec::new(),
|
||||
result_buffered_for: Duration::ZERO,
|
||||
error_text: None,
|
||||
};
|
||||
assert_eq!(
|
||||
@@ -564,13 +568,14 @@ fn failed_completion_delivery_rebuffers_the_event() {
|
||||
assert_eq!(response_rx.try_recv(), Ok(Ok(event)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn buffered_initial_yield_precedes_buffered_completion_for_yield_observer() {
|
||||
#[tokio::test(start_paused = true)]
|
||||
async fn buffered_initial_yield_precedes_buffered_completion_for_yield_observer() {
|
||||
let cell_state = CellState::new(CancellationToken::new());
|
||||
let completion = CellEvent::Completed {
|
||||
content_items: vec![OutputItem::Text {
|
||||
text: "after".to_string(),
|
||||
}],
|
||||
result_buffered_for: Duration::ZERO,
|
||||
error_text: None,
|
||||
};
|
||||
assert_eq!(
|
||||
@@ -610,8 +615,8 @@ fn buffered_initial_yield_precedes_buffered_completion_for_yield_observer() {
|
||||
assert_eq!(response_rx.try_recv(), Ok(Ok(completion)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pending_observer_merges_initial_yield_and_completion_output() {
|
||||
#[tokio::test(start_paused = true)]
|
||||
async fn pending_observer_merges_initial_yield_and_completion_output() {
|
||||
let cell_state = CellState::new(CancellationToken::new());
|
||||
assert_eq!(
|
||||
cell_state.commit_completion(
|
||||
@@ -619,6 +624,7 @@ fn pending_observer_merges_initial_yield_and_completion_output() {
|
||||
content_items: vec![OutputItem::Text {
|
||||
text: "after".to_string(),
|
||||
}],
|
||||
result_buffered_for: Duration::ZERO,
|
||||
error_text: None,
|
||||
},
|
||||
Some(vec![OutputItem::Text {
|
||||
@@ -633,6 +639,8 @@ fn pending_observer_merges_initial_yield_and_completion_output() {
|
||||
CompletionDelivery::Buffered
|
||||
));
|
||||
|
||||
let buffered = Duration::from_secs(30);
|
||||
tokio::time::advance(buffered).await;
|
||||
let (response_tx, mut response_rx) = oneshot::channel();
|
||||
assert!(matches!(
|
||||
cell_state.route_observation(ObserveMode::PendingFrontier, response_tx),
|
||||
@@ -649,18 +657,20 @@ fn pending_observer_merges_initial_yield_and_completion_output() {
|
||||
text: "after".to_string(),
|
||||
},
|
||||
],
|
||||
result_buffered_for: buffered,
|
||||
error_text: None,
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dropped_pending_observation_preserves_the_initial_yield_boundary() {
|
||||
#[tokio::test(start_paused = true)]
|
||||
async fn dropped_pending_observation_preserves_the_initial_yield_boundary() {
|
||||
let cell_state = CellState::new(CancellationToken::new());
|
||||
let completion = CellEvent::Completed {
|
||||
content_items: vec![OutputItem::Text {
|
||||
text: "after".to_string(),
|
||||
}],
|
||||
result_buffered_for: Duration::ZERO,
|
||||
error_text: None,
|
||||
};
|
||||
assert_eq!(
|
||||
|
||||
@@ -3,6 +3,7 @@ use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use tokio::time::Instant;
|
||||
|
||||
use serde_json::Value as JsonValue;
|
||||
use tokio::sync::mpsc;
|
||||
@@ -123,6 +124,7 @@ enum CellPhase {
|
||||
// Set only when `yield_control()` races the create-to-first-observe handoff.
|
||||
pending_initial_yield_items: Option<Vec<OutputItem>>,
|
||||
event: CellEvent,
|
||||
completed_at: Instant,
|
||||
},
|
||||
CompletionClaimed(CellEvent),
|
||||
Tombstone,
|
||||
@@ -186,8 +188,9 @@ impl CellState {
|
||||
CellPhase::Completed {
|
||||
pending_initial_yield_items,
|
||||
event,
|
||||
completed_at,
|
||||
} => {
|
||||
let event = prepend_initial_yield(event, pending_initial_yield_items);
|
||||
let event = buffered_completion(event, pending_initial_yield_items, completed_at);
|
||||
*phase = CellPhase::CompletionClaimed(event.clone());
|
||||
self.cancellation_token.cancel();
|
||||
ready_event(event)
|
||||
@@ -217,6 +220,7 @@ impl CellState {
|
||||
*phase = CellPhase::Completed {
|
||||
pending_initial_yield_items,
|
||||
event,
|
||||
completed_at: Instant::now(),
|
||||
};
|
||||
CompletionCommit::Committed
|
||||
}
|
||||
@@ -229,12 +233,13 @@ impl CellState {
|
||||
.phase
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
let (pending_initial_yield_items, event) =
|
||||
let (pending_initial_yield_items, event, completed_at) =
|
||||
match std::mem::replace(&mut *phase, CellPhase::Tombstone) {
|
||||
CellPhase::Completed {
|
||||
pending_initial_yield_items,
|
||||
event,
|
||||
} => (pending_initial_yield_items, event),
|
||||
completed_at,
|
||||
} => (pending_initial_yield_items, event, completed_at),
|
||||
previous => {
|
||||
*phase = previous;
|
||||
return CompletionDelivery::Rejected(response_tx);
|
||||
@@ -244,6 +249,7 @@ impl CellState {
|
||||
*phase = CellPhase::Completed {
|
||||
pending_initial_yield_items,
|
||||
event,
|
||||
completed_at,
|
||||
};
|
||||
return CompletionDelivery::Buffered;
|
||||
};
|
||||
@@ -256,6 +262,7 @@ impl CellState {
|
||||
*phase = CellPhase::Completed {
|
||||
pending_initial_yield_items,
|
||||
event,
|
||||
completed_at,
|
||||
};
|
||||
CompletionDelivery::Buffered
|
||||
}
|
||||
@@ -282,12 +289,14 @@ impl CellState {
|
||||
CellPhase::Completed {
|
||||
pending_initial_yield_items: Some(content_items),
|
||||
event,
|
||||
completed_at,
|
||||
} if matches!(mode, ObserveMode::YieldAfter(_)) => {
|
||||
match response_tx.send(Ok(CellEvent::Yielded { content_items })) {
|
||||
Ok(()) => {
|
||||
*phase = CellPhase::Completed {
|
||||
pending_initial_yield_items: None,
|
||||
event,
|
||||
completed_at,
|
||||
};
|
||||
ObservationDelivery::Buffered
|
||||
}
|
||||
@@ -295,6 +304,7 @@ impl CellState {
|
||||
*phase = CellPhase::Completed {
|
||||
pending_initial_yield_items: Some(content_items),
|
||||
event,
|
||||
completed_at,
|
||||
};
|
||||
ObservationDelivery::Buffered
|
||||
}
|
||||
@@ -309,9 +319,13 @@ impl CellState {
|
||||
CellPhase::Completed {
|
||||
pending_initial_yield_items,
|
||||
event,
|
||||
completed_at,
|
||||
} => {
|
||||
let delivered_event =
|
||||
prepend_initial_yield(event.clone(), pending_initial_yield_items.clone());
|
||||
let delivered_event = buffered_completion(
|
||||
event.clone(),
|
||||
pending_initial_yield_items.clone(),
|
||||
completed_at,
|
||||
);
|
||||
match response_tx.send(Ok(delivered_event)) {
|
||||
Ok(()) => {
|
||||
self.cancellation_token.cancel();
|
||||
@@ -321,6 +335,7 @@ impl CellState {
|
||||
*phase = CellPhase::Completed {
|
||||
pending_initial_yield_items,
|
||||
event,
|
||||
completed_at,
|
||||
};
|
||||
ObservationDelivery::Buffered
|
||||
}
|
||||
@@ -364,7 +379,12 @@ impl CellState {
|
||||
CellPhase::Completed {
|
||||
pending_initial_yield_items,
|
||||
event,
|
||||
} => Some(prepend_initial_yield(event, pending_initial_yield_items)),
|
||||
completed_at,
|
||||
} => Some(buffered_completion(
|
||||
event,
|
||||
pending_initial_yield_items,
|
||||
completed_at,
|
||||
)),
|
||||
CellPhase::CompletionClaimed(completed_event) => Some(completed_event),
|
||||
CellPhase::Tombstone => None,
|
||||
};
|
||||
@@ -385,47 +405,29 @@ impl CellState {
|
||||
}
|
||||
}
|
||||
|
||||
fn prepend_initial_yield(
|
||||
event: CellEvent,
|
||||
fn buffered_completion(
|
||||
mut event: CellEvent,
|
||||
pending_initial_yield_items: Option<Vec<OutputItem>>,
|
||||
completed_at: Instant,
|
||||
) -> CellEvent {
|
||||
let Some(mut pending_initial_yield_items) = pending_initial_yield_items else {
|
||||
return event;
|
||||
};
|
||||
match event {
|
||||
CellEvent::Yielded { mut content_items } => {
|
||||
pending_initial_yield_items.append(&mut content_items);
|
||||
CellEvent::Yielded {
|
||||
content_items: pending_initial_yield_items,
|
||||
}
|
||||
}
|
||||
CellEvent::Pending {
|
||||
mut content_items,
|
||||
pending_tool_call_ids,
|
||||
} => {
|
||||
pending_initial_yield_items.append(&mut content_items);
|
||||
CellEvent::Pending {
|
||||
content_items: pending_initial_yield_items,
|
||||
pending_tool_call_ids,
|
||||
}
|
||||
}
|
||||
let content_items = match &mut event {
|
||||
CellEvent::Completed {
|
||||
mut content_items,
|
||||
error_text,
|
||||
content_items,
|
||||
result_buffered_for,
|
||||
..
|
||||
} => {
|
||||
pending_initial_yield_items.append(&mut content_items);
|
||||
CellEvent::Completed {
|
||||
content_items: pending_initial_yield_items,
|
||||
error_text,
|
||||
}
|
||||
}
|
||||
CellEvent::Terminated { mut content_items } => {
|
||||
pending_initial_yield_items.append(&mut content_items);
|
||||
CellEvent::Terminated {
|
||||
content_items: pending_initial_yield_items,
|
||||
}
|
||||
*result_buffered_for = completed_at.elapsed();
|
||||
content_items
|
||||
}
|
||||
CellEvent::Yielded { content_items }
|
||||
| CellEvent::Pending { content_items, .. }
|
||||
| CellEvent::Terminated { content_items } => content_items,
|
||||
};
|
||||
if let Some(mut initial) = pending_initial_yield_items {
|
||||
initial.append(content_items);
|
||||
*content_items = initial;
|
||||
}
|
||||
event
|
||||
}
|
||||
|
||||
pub(super) enum CellCommand {
|
||||
|
||||
@@ -374,10 +374,12 @@ fn runtime_response(
|
||||
runtime::CellEvent::Completed {
|
||||
content_items,
|
||||
error_text,
|
||||
result_buffered_for,
|
||||
} => Ok(RuntimeResponse::Result {
|
||||
cell_id: cell_id.clone(),
|
||||
content_items: content_items.into_iter().map(output_item).collect(),
|
||||
error_text,
|
||||
result_buffered_for,
|
||||
code_mode_host_duration: None,
|
||||
}),
|
||||
runtime::CellEvent::Terminated { content_items } => Ok(RuntimeResponse::Terminated {
|
||||
@@ -414,6 +416,7 @@ fn output_item(item: runtime::OutputItem) -> FunctionCallOutputContentItem {
|
||||
fn missing_cell_response(cell_id: CellId) -> RuntimeResponse {
|
||||
RuntimeResponse::Result {
|
||||
error_text: Some(format!("exec cell {cell_id} not found")),
|
||||
result_buffered_for: Duration::ZERO,
|
||||
cell_id,
|
||||
content_items: Vec::new(),
|
||||
code_mode_host_duration: None,
|
||||
|
||||
@@ -80,6 +80,7 @@ audio({{type:"audio",mimeType:"audio/wav",data:{payload:?}}});"#
|
||||
code_mode_host_duration: None,
|
||||
cell_id: cell_id("1"),
|
||||
content_items: vec![expected; 3],
|
||||
result_buffered_for: std::time::Duration::ZERO,
|
||||
error_text: None,
|
||||
},
|
||||
"sample rate {sample_rate}, frames {frames}"
|
||||
@@ -145,21 +146,23 @@ async fn audio_helper_bounds_wav_data_and_preserves_outputs_after_yield() {
|
||||
expected.push(FunctionCallOutputContentItem::InputText {
|
||||
text: "after".to_string(),
|
||||
});
|
||||
let actual = service
|
||||
.wait(
|
||||
WaitRequest {
|
||||
cell_id: cell_id("1"),
|
||||
yield_time_ms: 60_000,
|
||||
},
|
||||
/*preempt*/ None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
service
|
||||
.wait(
|
||||
WaitRequest {
|
||||
cell_id: cell_id("1"),
|
||||
yield_time_ms: 60_000,
|
||||
},
|
||||
/*preempt*/ None,
|
||||
)
|
||||
.await
|
||||
.unwrap(),
|
||||
actual,
|
||||
WaitOutcome::LiveCell(RuntimeResponse::Result {
|
||||
code_mode_host_duration: None,
|
||||
cell_id: cell_id("1"),
|
||||
content_items: expected,
|
||||
result_buffered_for: actual.result_buffered_for().unwrap(),
|
||||
error_text: None,
|
||||
})
|
||||
);
|
||||
|
||||
@@ -220,21 +220,23 @@ async fn yielded_cells_retain_their_own_delegate_until_closed() {
|
||||
delegate_a.release_tool();
|
||||
assert_eq!(next_event(&mut events_a).await, DelegateEvent::ToolStarted);
|
||||
delegate_a.release_tool();
|
||||
let actual = service
|
||||
.wait(
|
||||
WaitRequest {
|
||||
cell_id: cell_id("1"),
|
||||
yield_time_ms: 60_000,
|
||||
},
|
||||
/*preempt*/ None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
service
|
||||
.wait(
|
||||
WaitRequest {
|
||||
cell_id: cell_id("1"),
|
||||
yield_time_ms: 60_000,
|
||||
},
|
||||
/*preempt*/ None,
|
||||
)
|
||||
.await
|
||||
.unwrap(),
|
||||
actual,
|
||||
WaitOutcome::LiveCell(RuntimeResponse::Result {
|
||||
code_mode_host_duration: None,
|
||||
cell_id: cell_id("1"),
|
||||
content_items: Vec::new(),
|
||||
result_buffered_for: actual.result_buffered_for().unwrap(),
|
||||
error_text: None
|
||||
})
|
||||
);
|
||||
@@ -288,23 +290,25 @@ async fn yields_and_resumes() {
|
||||
}],
|
||||
}
|
||||
);
|
||||
let actual = service
|
||||
.wait(
|
||||
WaitRequest {
|
||||
cell_id: cell_id("1"),
|
||||
yield_time_ms: 60_000,
|
||||
},
|
||||
/*preempt*/ None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
service
|
||||
.wait(
|
||||
WaitRequest {
|
||||
cell_id: cell_id("1"),
|
||||
yield_time_ms: 60_000,
|
||||
},
|
||||
/*preempt*/ None,
|
||||
)
|
||||
.await
|
||||
.unwrap(),
|
||||
actual,
|
||||
WaitOutcome::LiveCell(RuntimeResponse::Result {
|
||||
code_mode_host_duration: None,
|
||||
cell_id: cell_id("1"),
|
||||
content_items: vec![FunctionCallOutputContentItem::InputText {
|
||||
text: "after".to_string(),
|
||||
}],
|
||||
result_buffered_for: actual.result_buffered_for().unwrap(),
|
||||
error_text: None,
|
||||
})
|
||||
);
|
||||
@@ -356,6 +360,7 @@ text("after");
|
||||
content_items: vec![FunctionCallOutputContentItem::InputText {
|
||||
text: "after".to_string(),
|
||||
}],
|
||||
result_buffered_for: Duration::ZERO,
|
||||
error_text: None,
|
||||
}
|
||||
))
|
||||
@@ -413,14 +418,18 @@ async fn observed_natural_completion_wins_over_termination() {
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let actual = service.terminate(cell_id("1")).await.unwrap();
|
||||
let result_buffered_for = actual.result_buffered_for().unwrap();
|
||||
assert!(!result_buffered_for.is_zero());
|
||||
assert_eq!(
|
||||
service.terminate(cell_id("1")).await.unwrap(),
|
||||
actual,
|
||||
WaitOutcome::LiveCell(RuntimeResponse::Result {
|
||||
code_mode_host_duration: None,
|
||||
cell_id: cell_id("1"),
|
||||
content_items: vec![FunctionCallOutputContentItem::InputText {
|
||||
text: "done".to_string(),
|
||||
}],
|
||||
result_buffered_for,
|
||||
error_text: None,
|
||||
})
|
||||
);
|
||||
@@ -645,6 +654,7 @@ async fn natural_completion_cleans_up_callbacks_before_responding() {
|
||||
content_items: vec![FunctionCallOutputContentItem::InputText {
|
||||
text: "done".to_string(),
|
||||
}],
|
||||
result_buffered_for: Duration::ZERO,
|
||||
error_text: None,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -139,6 +139,7 @@ async fn interrupt_yields_execute_and_wait_without_stopping_the_cell() {
|
||||
content_items: vec![FunctionCallOutputContentItem::InputText {
|
||||
text: "done".to_string(),
|
||||
}],
|
||||
result_buffered_for: Duration::ZERO,
|
||||
error_text: None,
|
||||
})
|
||||
);
|
||||
@@ -173,6 +174,7 @@ async fn execute_waits_for_nested_tool_during_yield_grace() {
|
||||
content_items: vec![FunctionCallOutputContentItem::InputText {
|
||||
text: "done".to_string(),
|
||||
}],
|
||||
result_buffered_for: Duration::ZERO,
|
||||
error_text: None,
|
||||
}
|
||||
);
|
||||
@@ -254,6 +256,7 @@ async fn execute_and_wait_clamp_yield_grace_without_stopping_the_cell() {
|
||||
content_items: vec![FunctionCallOutputContentItem::InputText {
|
||||
text: "done".to_string(),
|
||||
}],
|
||||
result_buffered_for: Duration::ZERO,
|
||||
error_text: None,
|
||||
})
|
||||
);
|
||||
@@ -306,6 +309,7 @@ async fn wait_waits_for_nested_tool_during_yield_grace() {
|
||||
content_items: vec![FunctionCallOutputContentItem::InputText {
|
||||
text: "done".to_string(),
|
||||
}],
|
||||
result_buffered_for: Duration::ZERO,
|
||||
error_text: None,
|
||||
})
|
||||
);
|
||||
@@ -415,6 +419,7 @@ async fn tool_start_acknowledgement_reports_error_before_invocation() {
|
||||
code_mode_host_duration: None,
|
||||
cell_id: cell_id("1"),
|
||||
content_items: Vec::new(),
|
||||
result_buffered_for: Duration::ZERO,
|
||||
error_text: Some("startup failed".to_string()),
|
||||
}
|
||||
);
|
||||
@@ -572,6 +577,7 @@ async fn synchronous_exit_returns_successfully() {
|
||||
content_items: vec![FunctionCallOutputContentItem::InputText {
|
||||
text: "before".to_string(),
|
||||
}],
|
||||
result_buffered_for: Duration::ZERO,
|
||||
error_text: None,
|
||||
}
|
||||
);
|
||||
@@ -667,6 +673,7 @@ throw {
|
||||
content_items: vec![FunctionCallOutputContentItem::InputText {
|
||||
text: "before".to_string(),
|
||||
}],
|
||||
result_buffered_for: Duration::ZERO,
|
||||
error_text: None,
|
||||
},
|
||||
"{label}"
|
||||
@@ -688,6 +695,7 @@ throw {
|
||||
content_items: vec![FunctionCallOutputContentItem::InputText {
|
||||
text: "before".to_string(),
|
||||
}],
|
||||
result_buffered_for: Duration::ZERO,
|
||||
error_text: None,
|
||||
},
|
||||
"{label}: next cell"
|
||||
@@ -735,23 +743,25 @@ text("after exit");
|
||||
}
|
||||
);
|
||||
delegate.release_tool();
|
||||
let actual = service
|
||||
.wait(
|
||||
WaitRequest {
|
||||
cell_id: cell_id("1"),
|
||||
yield_time_ms: 30_000,
|
||||
},
|
||||
/*preempt*/ None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
service
|
||||
.wait(
|
||||
WaitRequest {
|
||||
cell_id: cell_id("1"),
|
||||
yield_time_ms: 30_000,
|
||||
},
|
||||
/*preempt*/ None,
|
||||
)
|
||||
.await
|
||||
.unwrap(),
|
||||
actual,
|
||||
WaitOutcome::LiveCell(RuntimeResponse::Result {
|
||||
code_mode_host_duration: None,
|
||||
cell_id: cell_id("1"),
|
||||
content_items: vec![FunctionCallOutputContentItem::InputText {
|
||||
text: "after tool".to_string(),
|
||||
}],
|
||||
result_buffered_for: actual.result_buffered_for().unwrap(),
|
||||
error_text: None,
|
||||
})
|
||||
);
|
||||
@@ -770,6 +780,7 @@ text("after exit");
|
||||
content_items: vec![FunctionCallOutputContentItem::InputText {
|
||||
text: "after tool".to_string(),
|
||||
}],
|
||||
result_buffered_for: Duration::ZERO,
|
||||
error_text: None,
|
||||
}
|
||||
);
|
||||
@@ -815,6 +826,7 @@ async fn stored_values_are_shared_between_cells_but_not_sessions() {
|
||||
code_mode_host_duration: None,
|
||||
cell_id: cell_id("1"),
|
||||
content_items: Vec::new(),
|
||||
result_buffered_for: Duration::ZERO,
|
||||
error_text: None,
|
||||
}
|
||||
);
|
||||
@@ -826,6 +838,7 @@ async fn stored_values_are_shared_between_cells_but_not_sessions() {
|
||||
content_items: vec![FunctionCallOutputContentItem::InputText {
|
||||
text: "visible".to_string(),
|
||||
}],
|
||||
result_buffered_for: Duration::ZERO,
|
||||
error_text: None,
|
||||
}
|
||||
);
|
||||
@@ -837,6 +850,7 @@ async fn stored_values_are_shared_between_cells_but_not_sessions() {
|
||||
content_items: vec![FunctionCallOutputContentItem::InputText {
|
||||
text: "undefined".to_string(),
|
||||
}],
|
||||
result_buffered_for: Duration::ZERO,
|
||||
error_text: None,
|
||||
}
|
||||
);
|
||||
@@ -879,6 +893,7 @@ text(load("key"));
|
||||
text: "null".to_string(),
|
||||
},
|
||||
],
|
||||
result_buffered_for: Duration::ZERO,
|
||||
error_text: None,
|
||||
}
|
||||
);
|
||||
@@ -1055,6 +1070,7 @@ async fn execute_to_pending_returns_completed_for_synchronous_results() {
|
||||
content_items: vec![FunctionCallOutputContentItem::InputText {
|
||||
text: "done".to_string(),
|
||||
}],
|
||||
result_buffered_for: Duration::ZERO,
|
||||
error_text: None,
|
||||
})
|
||||
);
|
||||
@@ -1333,6 +1349,7 @@ text("done");
|
||||
content_items: vec![FunctionCallOutputContentItem::InputText {
|
||||
text: "done".to_string(),
|
||||
}],
|
||||
result_buffered_for: Duration::ZERO,
|
||||
error_text: None,
|
||||
}
|
||||
))
|
||||
@@ -1481,6 +1498,7 @@ async fn v8_console_is_not_exposed_on_global_this() {
|
||||
content_items: vec![FunctionCallOutputContentItem::InputText {
|
||||
text: "false".to_string(),
|
||||
}],
|
||||
result_buffered_for: Duration::ZERO,
|
||||
error_text: None,
|
||||
}
|
||||
);
|
||||
@@ -1522,6 +1540,7 @@ text(value);
|
||||
content_items: vec![FunctionCallOutputContentItem::InputText {
|
||||
text: "jeudi 2 janvier \u{e0} 03:04:05".to_string(),
|
||||
}],
|
||||
result_buffered_for: Duration::ZERO,
|
||||
error_text: None,
|
||||
}
|
||||
);
|
||||
@@ -1562,6 +1581,7 @@ text(formatter.format(new Date("2025-01-02T03:04:05Z")));
|
||||
content_items: vec![FunctionCallOutputContentItem::InputText {
|
||||
text: "jeudi 2 janvier \u{e0} 03:04:05".to_string(),
|
||||
}],
|
||||
result_buffered_for: Duration::ZERO,
|
||||
error_text: None,
|
||||
}
|
||||
);
|
||||
@@ -1610,6 +1630,7 @@ text(JSON.stringify(returnsUndefined));
|
||||
text: "[true,true,true,true]".to_string(),
|
||||
},
|
||||
],
|
||||
result_buffered_for: Duration::ZERO,
|
||||
error_text: None,
|
||||
}
|
||||
);
|
||||
@@ -1637,6 +1658,7 @@ async fn text_helper_serializes_objects() {
|
||||
content_items: vec![FunctionCallOutputContentItem::InputText {
|
||||
text: r#"{"json":true}"#.to_string(),
|
||||
}],
|
||||
result_buffered_for: Duration::ZERO,
|
||||
error_text: None,
|
||||
}
|
||||
);
|
||||
@@ -1686,6 +1708,7 @@ async fn output_helpers_surface_serialization_errors() {
|
||||
code_mode_host_duration: None,
|
||||
cell_id: cell_id("1"),
|
||||
content_items: Vec::new(),
|
||||
result_buffered_for: Duration::ZERO,
|
||||
error_text: Some(error_text),
|
||||
}
|
||||
);
|
||||
@@ -1729,6 +1752,7 @@ audio({
|
||||
audio_url: "data:audio/wav;base64,YXVkaW8=".to_string(),
|
||||
},
|
||||
],
|
||||
result_buffered_for: Duration::ZERO,
|
||||
error_text: None,
|
||||
}
|
||||
);
|
||||
@@ -1761,6 +1785,7 @@ async fn audio_helper_rejects_non_data_urls() {
|
||||
code_mode_host_duration: None,
|
||||
cell_id: cell_id("1"),
|
||||
content_items: Vec::new(),
|
||||
result_buffered_for: Duration::ZERO,
|
||||
error_text: Some(
|
||||
"Tool call failed: invalid audio output. Pass a base64 data URI instead"
|
||||
.to_string(),
|
||||
@@ -1798,6 +1823,7 @@ image({
|
||||
image_url: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4nGP4z8DwHwAFAAH/iZk9HQAAAABJRU5ErkJggg==".to_string(),
|
||||
detail: Some(crate::ImageDetail::Original),
|
||||
}],
|
||||
result_buffered_for: Duration::ZERO,
|
||||
error_text: None,
|
||||
}
|
||||
);
|
||||
@@ -1837,6 +1863,7 @@ generatedImage({
|
||||
text: "generated image save hint".to_string(),
|
||||
},
|
||||
],
|
||||
result_buffered_for: Duration::ZERO,
|
||||
error_text: None,
|
||||
}
|
||||
);
|
||||
@@ -1874,6 +1901,7 @@ image(
|
||||
image_url: "data:image/png;base64,AAA".to_string(),
|
||||
detail: Some(crate::ImageDetail::Original),
|
||||
}],
|
||||
result_buffered_for: Duration::ZERO,
|
||||
error_text: None,
|
||||
}
|
||||
);
|
||||
@@ -1910,6 +1938,7 @@ image(
|
||||
image_url: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4nGP4z8DwHwAFAAH/iZk9HQAAAABJRU5ErkJggg==".to_string(),
|
||||
detail: Some(crate::ImageDetail::High),
|
||||
}],
|
||||
result_buffered_for: Duration::ZERO,
|
||||
error_text: None,
|
||||
}
|
||||
);
|
||||
@@ -1944,6 +1973,7 @@ image({
|
||||
image_url: "data:image/png;base64,AAA".to_string(),
|
||||
detail: Some(crate::ImageDetail::Low),
|
||||
}],
|
||||
result_buffered_for: Duration::ZERO,
|
||||
error_text: None,
|
||||
}
|
||||
);
|
||||
@@ -1977,6 +2007,7 @@ async fn image_helpers_reject_remote_urls() {
|
||||
code_mode_host_duration: None,
|
||||
cell_id: cell_id("1"),
|
||||
content_items: Vec::new(),
|
||||
result_buffered_for: Duration::ZERO,
|
||||
error_text: Some(
|
||||
"Tool call failed: remote image URLs are not supported in tool outputs. Pass a base64 data URI instead".to_string(),
|
||||
),
|
||||
@@ -2012,6 +2043,7 @@ async fn image_helpers_reject_invalid_image_outputs() {
|
||||
code_mode_host_duration: None,
|
||||
cell_id: cell_id("1"),
|
||||
content_items: Vec::new(),
|
||||
result_buffered_for: Duration::ZERO,
|
||||
error_text: Some(
|
||||
"Tool call failed: invalid image output. Pass a base64 data URI instead"
|
||||
.to_string(),
|
||||
@@ -2047,6 +2079,7 @@ image({
|
||||
code_mode_host_duration: None,
|
||||
cell_id: cell_id("1"),
|
||||
content_items: Vec::new(),
|
||||
result_buffered_for: Duration::ZERO,
|
||||
error_text: Some("image detail must be one of: auto, low, high, original".to_string()),
|
||||
}
|
||||
);
|
||||
@@ -2082,6 +2115,7 @@ image({
|
||||
code_mode_host_duration: None,
|
||||
cell_id: cell_id("1"),
|
||||
content_items: Vec::new(),
|
||||
result_buffered_for: Duration::ZERO,
|
||||
error_text: Some(
|
||||
"image expects a non-empty image URL string, an object with image_url and optional detail, or a raw MCP image block".to_string(),
|
||||
),
|
||||
@@ -2110,6 +2144,7 @@ async fn wait_reports_missing_cell_separately_from_runtime_results() {
|
||||
code_mode_host_duration: None,
|
||||
cell_id: cell_id("missing"),
|
||||
content_items: Vec::new(),
|
||||
result_buffered_for: Duration::ZERO,
|
||||
error_text: Some("exec cell missing not found".to_string()),
|
||||
})
|
||||
);
|
||||
|
||||
@@ -83,6 +83,7 @@ async fn reports_cell_actor_panics_to_the_owner() {
|
||||
content_items: vec![OutputItem::Text {
|
||||
text: "done".to_string(),
|
||||
}],
|
||||
result_buffered_for: Duration::ZERO,
|
||||
error_text: None,
|
||||
})
|
||||
);
|
||||
@@ -107,6 +108,7 @@ async fn termination_rejects_a_waiting_store_commit_before_the_next_cell_can_loa
|
||||
content_items: vec![OutputItem::Text {
|
||||
text: "uncommitted output".to_string(),
|
||||
}],
|
||||
result_buffered_for: Duration::ZERO,
|
||||
error_text: None,
|
||||
};
|
||||
|
||||
@@ -163,6 +165,7 @@ async fn termination_rejects_a_waiting_store_commit_before_the_next_cell_can_loa
|
||||
content_items: vec![OutputItem::Text {
|
||||
text: "undefined".to_string(),
|
||||
}],
|
||||
result_buffered_for: Duration::ZERO,
|
||||
error_text: None,
|
||||
})
|
||||
);
|
||||
|
||||
@@ -66,6 +66,8 @@ pub(crate) enum CellEvent {
|
||||
Completed {
|
||||
content_items: Vec<OutputItem>,
|
||||
error_text: Option<String>,
|
||||
/// Time the completed result spent waiting for an observer.
|
||||
result_buffered_for: Duration,
|
||||
},
|
||||
Terminated {
|
||||
content_items: Vec<OutputItem>,
|
||||
|
||||
@@ -79,6 +79,7 @@ text(JSON.stringify([3, 1, 2].sort((a, b) => a - b)));
|
||||
content_items: vec![FunctionCallOutputContentItem::InputText {
|
||||
text: "[1,2,3]".to_string(),
|
||||
}],
|
||||
result_buffered_for: std::time::Duration::ZERO,
|
||||
error_text: None,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -38,6 +38,7 @@ async fn code_mode_runs_with_jit_disabled() {
|
||||
code_mode_host_duration: None,
|
||||
cell_id,
|
||||
content_items: Vec::new(),
|
||||
result_buffered_for: std::time::Duration::ZERO,
|
||||
error_text: None,
|
||||
}
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user