Preserve code mode cancellation during output serialization (#52685)

## Why

Reentering V8 during termination can replace uncatchable cancellation with a catchable JavaScript exception, allowing a script to continue after cancellation.

## What changed

Skip JSON conversion and exception throwing while V8 execution is terminating, preserving cancellation through `image()` getters and JSON fallback.

## Testing

Add regression coverage for cancellation during JSON fallback and reentrant image getters, including prompt termination through the standalone host and continued usability of another session. Extend serialization error tests to cover failing image getters alongside circular JSON.

GitOrigin-RevId: f03dd131a62724106fad96111292ddf5b9dc99f6
This commit is contained in:
cassirer-openai
2026-10-09 20:01:48 +00:00
committed by copyberry
parent 52950c5924
commit c261727370
5 changed files with 207 additions and 67 deletions
+45 -37
View File
@@ -1445,45 +1445,53 @@ async fn text_helper_serializes_objects() {
}
#[tokio::test]
async fn text_helper_surfaces_stringify_errors() {
let service = InProcessCodeModeSession::new();
async fn output_helpers_surface_serialization_errors() {
for (source, expected_error) in [
(
"const circular = {}; circular.self = circular; text(circular);",
"Converting circular structure to JSON",
),
(
"image({get image_url() { throw new Error('image getter failed'); }});",
"image getter failed",
),
(
"image({type: 'image', get data() { throw new Error('MCP getter failed'); }});",
"MCP getter failed",
),
] {
let service = InProcessCodeModeSession::new();
let response = execute(
&service,
ExecuteRequest {
yield_time_ms: None,
..execute_request(source)
},
)
.await;
let response = execute(
&service,
ExecuteRequest {
source: r#"
const circular = {};
circular.self = circular;
text(circular);
"#
.to_string(),
yield_time_ms: None,
..execute_request("")
},
)
.await;
let RuntimeResponse::Result {
error_text: Some(error_text),
..
} = &response
else {
panic!("circular stringify unexpectedly succeeded: {response:?}");
};
assert!(
error_text.contains("Converting circular structure to JSON"),
"unexpected circular stringify error: {error_text}"
);
let error_text = error_text.clone();
assert_eq!(
response,
RuntimeResponse::Result {
code_mode_host_duration: None,
cell_id: cell_id("1"),
content_items: Vec::new(),
let RuntimeResponse::Result {
error_text: Some(error_text),
}
);
..
} = &response
else {
panic!("serialization unexpectedly succeeded: {response:?}");
};
assert!(
error_text.contains(expected_error),
"unexpected serialization error: {error_text}"
);
let error_text = error_text.clone();
assert_eq!(
response,
RuntimeResponse::Result {
code_mode_host_duration: None,
cell_id: cell_id("1"),
content_items: Vec::new(),
error_text: Some(error_text),
}
);
}
}
#[tokio::test]