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:
Ruslan Nigmatullin
2026-10-20 18:53:06 +00:00
committed by copyberry
parent 801bd4d773
commit 87f3c3284e
38 changed files with 397 additions and 123 deletions
@@ -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,
}
);
+45 -10
View File
@@ -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,
}
);
+1
View File
@@ -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,
}
);