use codex_code_mode_protocol::NoopCodeModeSessionDelegate; use std::sync::Arc; use std::time::Duration; use super::CellId; use super::CodeModeNestedToolCall; use super::CodeModeSessionDelegate; use super::InProcessCodeModeSession; use super::RuntimeResponse; use super::WaitOutcome; use super::WaitRequest; use super::WaitToPendingOutcome; use super::WaitToPendingRequest; use crate::CodeModeToolKind; use crate::ExecuteRequest; use crate::ExecuteToPendingOutcome; use crate::FunctionCallOutputContentItem; use crate::ToolDefinition; use codex_code_mode_protocol::CodeModeSession; use codex_code_mode_protocol::CodeModeSessionCellExecutionLimits; use codex_code_mode_protocol::NotificationFuture; use codex_code_mode_protocol::ToolInvocationFuture; use codex_protocol::ToolName; use pretty_assertions::assert_eq; use serde_json::Value as JsonValue; use tokio::sync::Notify; use tokio::sync::oneshot; use tokio_util::sync::CancellationToken; #[test] fn resolve_yield_timeout_applies_grace_before_session_limits() { for (max_yield_time_ms, requested_yield_time_ms, expected_timeout) in [ (None, 0, Duration::ZERO), (None, 9_999, Duration::from_millis(9_999)), (None, 10_000, Duration::from_secs(11)), (None, 10_001, Duration::from_millis(11_001)), (Some(0), 0, Duration::ZERO), (Some(0), 10_000, Duration::ZERO), (Some(5_000), 9_999, Duration::from_secs(5)), (Some(10_000), 10_000, Duration::from_secs(10)), (Some(10_500), 10_000, Duration::from_millis(10_500)), (Some(11_000), 10_000, Duration::from_secs(11)), (Some(12_000), 10_000, Duration::from_secs(11)), (Some(10_500), 5_000, Duration::from_secs(5)), (Some(u64::MAX), u64::MAX, Duration::from_millis(u64::MAX)), ] { let session = InProcessCodeModeSession::with_limits(CodeModeSessionCellExecutionLimits { max_yield_time_ms, max_heap_size_bytes: None, }); assert_eq!( session.resolve_yield_timeout(requested_yield_time_ms), expected_timeout, "requested {requested_yield_time_ms} ms with limit {max_yield_time_ms:?}" ); } } #[tokio::test(start_paused = true)] async fn interrupt_yields_execute_and_wait_without_stopping_the_cell() { let _time_guard = hold_virtual_time(); let delegate = Arc::new(ReleasableToolDelegate::default()); let service: Arc = Arc::new(InProcessCodeModeSession::new()); let execute_signal = CancellationToken::new(); let started = service .execute( ExecuteRequest { enabled_tools: vec![echo_tool()], source: r#"await tools.echo({}); text("done");"#.to_string(), yield_time_ms: Some(60_000), ..execute_request("") }, delegate.clone(), Some(execute_signal.clone()), ) .await .unwrap(); let response = tokio::spawn(started.initial_response()); wait_until_tool_started(&delegate).await.unwrap(); execute_signal.cancel(); assert_eq!( response.await.unwrap().unwrap(), RuntimeResponse::Yielded { code_mode_host_duration: None, cell_id: cell_id("1"), content_items: Vec::new(), } ); let wait_signal = CancellationToken::new(); let response = tokio::spawn({ let service = Arc::clone(&service); let wait_signal = wait_signal.clone(); async move { service .wait( WaitRequest { cell_id: cell_id("1"), yield_time_ms: 60_000, max_output_tokens: None, }, Some(wait_signal), ) .await } }); tokio::task::yield_now().await; assert!(!response.is_finished()); wait_signal.cancel(); assert_eq!( response.await.unwrap().unwrap(), WaitOutcome::LiveCell(RuntimeResponse::Yielded { code_mode_host_duration: None, cell_id: cell_id("1"), content_items: Vec::new(), }) ); delegate.release_tool(); let response = tokio::spawn({ let service = Arc::clone(&service); async move { service .wait( WaitRequest { cell_id: cell_id("1"), yield_time_ms: 60_000, max_output_tokens: None, }, /*preempt*/ None, ) .await } }); assert_eq!( response.await.unwrap().unwrap(), 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: Duration::ZERO, error_text: None, }) ); } #[tokio::test(start_paused = true)] async fn execute_waits_for_nested_tool_during_yield_grace() { let _time_guard = hold_virtual_time(); let delegate = Arc::new(ReleasableToolDelegate::default()); let service = InProcessCodeModeSession::new(); let request = ExecuteRequest { enabled_tools: vec![echo_tool()], source: r#"await tools.echo({}); text("done");"#.to_string(), yield_time_ms: Some(10_000), ..execute_request("") }; let started = service .execute(request, delegate.clone(), /*preempt*/ None) .await .unwrap(); let response = tokio::spawn(started.initial_response()); wait_until_tool_started(&delegate).await.unwrap(); tokio::time::advance(Duration::from_millis(10_500)).await; delegate.release_tool(); let response = response.await.unwrap().unwrap(); assert_eq!( response, RuntimeResponse::Result { code_mode_host_duration: None, cell_id: cell_id("1"), content_items: vec![FunctionCallOutputContentItem::InputText { text: "done".to_string(), }], result_buffered_for: Duration::ZERO, error_text: None, } ); } #[tokio::test(start_paused = true)] async fn execute_and_wait_clamp_yield_grace_without_stopping_the_cell() { let _time_guard = hold_virtual_time(); let delegate = Arc::new(ReleasableToolDelegate::default()); let service = InProcessCodeModeSession::with_limits(CodeModeSessionCellExecutionLimits { max_yield_time_ms: Some(/*value*/ 10_000), max_heap_size_bytes: None, }); let started = service .execute( ExecuteRequest { enabled_tools: vec![echo_tool()], source: r#"await tools.echo({}); text("done");"#.to_string(), yield_time_ms: None, ..execute_request("") }, delegate.clone(), /*preempt*/ None, ) .await .unwrap(); let initial_response = tokio::spawn(started.initial_response()); wait_until_tool_started(&delegate).await.unwrap(); tokio::time::advance(Duration::from_millis(9_999)).await; assert!(!initial_response.is_finished()); tokio::time::advance(Duration::from_millis(/*millis*/ 1)).await; assert_eq!( initial_response.await.unwrap().unwrap(), RuntimeResponse::Yielded { code_mode_host_duration: None, cell_id: cell_id("1"), content_items: Vec::new(), } ); let wait_response = service .begin_wait( WaitRequest { cell_id: cell_id("1"), yield_time_ms: 10_000, max_output_tokens: None, }, /*preempt*/ None, ) .await; let wait_response = tokio::spawn(wait_response); tokio::task::yield_now().await; tokio::time::advance(Duration::from_secs(/*secs*/ 10)).await; assert_eq!( wait_response.await.unwrap().unwrap(), WaitOutcome::LiveCell(RuntimeResponse::Yielded { code_mode_host_duration: None, cell_id: cell_id("1"), content_items: Vec::new(), }) ); delegate.release_tool(); let completion = service .begin_wait( WaitRequest { cell_id: cell_id("1"), yield_time_ms: 10_000, max_output_tokens: None, }, /*preempt*/ None, ) .await; let completion = tokio::spawn(completion); assert_eq!( completion.await.unwrap().unwrap(), 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: Duration::ZERO, error_text: None, }) ); } #[tokio::test(start_paused = true)] async fn wait_waits_for_nested_tool_during_yield_grace() { let _time_guard = hold_virtual_time(); let delegate = Arc::new(ReleasableToolDelegate::default()); let service = InProcessCodeModeSession::new(); let initial_response = service .execute_to_pending( ExecuteRequest { enabled_tools: vec![echo_tool()], source: r#"await tools.echo({}); text("done");"#.to_string(), ..execute_request("") }, delegate.clone(), ) .await .unwrap(); assert_eq!( initial_response, ExecuteToPendingOutcome::Pending { cell_id: cell_id("1"), content_items: Vec::new(), pending_tool_call_ids: vec!["tool-1".to_string()], } ); let response = service .begin_wait( WaitRequest { cell_id: cell_id("1"), yield_time_ms: 10_000, max_output_tokens: None, }, /*preempt*/ None, ) .await; let response = tokio::spawn(response); tokio::task::yield_now().await; tokio::time::advance(Duration::from_millis(10_500)).await; delegate.release_tool(); let response = response.await.unwrap(); assert_eq!( response.unwrap(), 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: Duration::ZERO, error_text: None, }) ); } #[tokio::test(start_paused = true)] async fn zero_yield_limit_is_immediate_and_scoped_to_its_session() { let _time_guard = hold_virtual_time(); let zero_delegate = Arc::new(ReleasableToolDelegate::default()); let zero_session = InProcessCodeModeSession::with_limits(CodeModeSessionCellExecutionLimits { max_yield_time_ms: Some(/*value*/ 0), max_heap_size_bytes: None, }); let limited_delegate = Arc::new(ReleasableToolDelegate::default()); let limited_session = InProcessCodeModeSession::with_limits(CodeModeSessionCellExecutionLimits { max_yield_time_ms: Some(/*value*/ 10), max_heap_size_bytes: None, }); let request = ExecuteRequest { enabled_tools: vec![echo_tool()], source: "await tools.echo({});".to_string(), yield_time_ms: Some(/*value*/ 60_000), ..execute_request("") }; let zero_started = zero_session .execute( request.clone(), zero_delegate.clone(), /*preempt*/ None, ) .await .unwrap(); let limited_started = limited_session .execute(request, limited_delegate.clone(), /*preempt*/ None) .await .unwrap(); let zero_response = tokio::spawn(zero_started.initial_response()); let limited_response = tokio::spawn(limited_started.initial_response()); wait_until_tool_started(&zero_delegate).await.unwrap(); wait_until_tool_started(&limited_delegate).await.unwrap(); assert!(!limited_response.is_finished()); assert_eq!( zero_response.await.unwrap().unwrap(), RuntimeResponse::Yielded { code_mode_host_duration: None, cell_id: cell_id("1"), content_items: Vec::new(), } ); let zero_wait = zero_session .begin_wait( WaitRequest { cell_id: cell_id("1"), yield_time_ms: 60_000, max_output_tokens: None, }, /*preempt*/ None, ) .await; let zero_wait = tokio::spawn(zero_wait); assert_eq!( zero_wait.await.unwrap().unwrap(), WaitOutcome::LiveCell(RuntimeResponse::Yielded { code_mode_host_duration: None, cell_id: cell_id("1"), content_items: Vec::new(), }) ); tokio::time::advance(Duration::from_millis(/*millis*/ 10)).await; assert_eq!( limited_response.await.unwrap().unwrap(), RuntimeResponse::Yielded { code_mode_host_duration: None, cell_id: cell_id("1"), content_items: Vec::new(), } ); zero_session.shutdown().await.unwrap(); limited_session.shutdown().await.unwrap(); } #[tokio::test(start_paused = true)] async fn tool_start_acknowledgement_reports_error_before_invocation() { let _time_guard = hold_virtual_time(); let delegate = Arc::new(ReleasableToolDelegate::default()); let service = InProcessCodeModeSession::new(); let started = service .execute( execute_request("throw 'startup failed';"), delegate.clone(), /*preempt*/ None, ) .await .unwrap(); let (startup, response) = tokio::join!( wait_until_tool_started(&delegate), started.initial_response(), ); assert_eq!(startup, Err("cell closed before nested tool started")); assert_eq!( response.unwrap(), RuntimeResponse::Result { 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()), } ); } #[tokio::test(start_paused = true)] async fn tool_start_acknowledgement_reports_termination_before_invocation() { let _time_guard = hold_virtual_time(); let delegate = Arc::new(ReleasableToolDelegate::default()); let service = InProcessCodeModeSession::new(); let started = service .execute( execute_request("await new Promise(() => {});"), delegate.clone(), /*preempt*/ None, ) .await .unwrap(); let (startup, terminated) = tokio::join!( wait_until_tool_started(&delegate), service.terminate(cell_id("1").into()), ); assert_eq!(startup, Err("cell closed before nested tool started")); let response = RuntimeResponse::Terminated { code_mode_host_duration: None, cell_id: cell_id("1"), content_items: Vec::new(), }; assert_eq!(terminated.unwrap(), WaitOutcome::LiveCell(response.clone())); assert_eq!(started.initial_response().await.unwrap(), response); } fn hold_virtual_time() -> oneshot::Sender<()> { // A live blocking task prevents Tokio's paused clock from auto-advancing while // we await the V8 thread. Tests advance time explicitly; dropping the guard // releases the task even if the test panics or is cancelled. let (guard, released) = oneshot::channel(); tokio::task::spawn_blocking(move || { let _ = released.blocking_recv(); }); guard } async fn wait_until_tool_started(delegate: &ReleasableToolDelegate) -> Result<(), &'static str> { tokio::select! { biased; _ = delegate.tool_started.notified() => Ok(()), _ = delegate.cell_closed.cancelled() => Err("cell closed before nested tool started"), } } #[derive(Default)] struct ReleasableToolDelegate { tool_release: Notify, tool_started: Notify, cell_closed: CancellationToken, } impl ReleasableToolDelegate { fn release_tool(&self) { self.tool_release.notify_one(); } } impl CodeModeSessionDelegate for ReleasableToolDelegate { fn invoke_tool<'a>( &'a self, _invocation: CodeModeNestedToolCall, cancellation_token: CancellationToken, ) -> ToolInvocationFuture<'a> { self.tool_started.notify_one(); Box::pin(async move { tokio::select! { _ = self.tool_release.notified() => Ok(JsonValue::Null), _ = cancellation_token.cancelled() => Err("cancelled".to_string()), } }) } fn notify<'a>( &'a self, _call_id: String, _cell_id: CellId, _text: String, _cancellation_token: CancellationToken, ) -> NotificationFuture<'a> { Box::pin(async { Ok(()) }) } fn cell_closed(&self, _cell_id: &CellId) { self.cell_closed.cancel(); } } fn execute_request(source: &str) -> ExecuteRequest { ExecuteRequest { tool_call_id: "call_1".to_string(), enabled_tools: Vec::new(), source: source.to_string(), yield_time_ms: Some(1), max_output_tokens: None, } } fn cell_id(value: &str) -> CellId { CellId::new(value.to_string()) } fn echo_tool() -> ToolDefinition { ToolDefinition { name: "echo".to_string(), tool_name: ToolName::plain("echo"), description: String::new(), kind: CodeModeToolKind::Function, input_schema: None, input_schema_max_bytes: None, output_schema: None, } } async fn execute(service: &InProcessCodeModeSession, request: ExecuteRequest) -> RuntimeResponse { service .execute( request, Arc::new(NoopCodeModeSessionDelegate), /*preempt*/ None, ) .await .unwrap() .initial_response() .await .unwrap() } #[tokio::test] async fn synchronous_exit_returns_successfully() { let service = InProcessCodeModeSession::new(); let response = execute( &service, ExecuteRequest { source: r#"text("before"); exit(); text("after");"#.to_string(), yield_time_ms: None, ..execute_request("") }, ) .await; assert_eq!( response, RuntimeResponse::Result { code_mode_host_duration: None, cell_id: cell_id("1"), content_items: vec![FunctionCallOutputContentItem::InputText { text: "before".to_string(), }], result_buffered_for: Duration::ZERO, error_text: None, } ); } #[tokio::test] async fn exit_stops_the_whole_cell_and_preserves_only_prior_writes() { for (label, source) in [ ("exit inside infinite loop", r#"for (;;) { exit(); }"#), ("infinite loop after exit", r#"exit(); for (;;) {}"#), ( "invalid helper after exit", r#"try { exit(); image(null); } catch { for (;;) {} }"#, ), ( "catch and finally", r#"try { exit(); } catch { text("caught"); } finally { store("phase", "finally"); text("finally"); }"#, ), ( "queued microtask", r#"Promise.resolve().then(() => { store("phase", "microtask"); text("microtask"); }); exit();"#, ), ( "unawaited promise", r#"Promise.resolve().then(() => exit()).catch(() => text("caught")); await new Promise(() => {});"#, ), ( "async module", r#"await Promise.resolve(); try { exit(); } finally { text("finally"); }"#, ), ( "parallel promises", r#"await Promise.all([Promise.resolve().then(() => exit()), Promise.resolve().then(() => { store("phase", "sibling"); text("sibling"); })]);"#, ), ( "timer callback", r#"setTimeout(() => { try { exit(); } catch { text("caught"); } finally { text("finally"); } }, 0); await new Promise(() => {});"#, ), ( "timer microtask", r#"setTimeout(() => { Promise.resolve().then(() => exit()); }, 0); await new Promise(() => {});"#, ), ( "after awaited timer", r#"await new Promise((resolve) => setTimeout(resolve, 0)); try { exit(); } catch { text("caught"); }"#, ), ( "text toJSON", r#"try { text({ toJSON() { exit(); } }); } catch { text("caught"); } finally { text("finally"); }"#, ), ( "store toJSON", r#"try { store("phase", { toJSON() { exit(); } }); } catch { text("caught"); } finally { text("finally"); }"#, ), ( "store key coercion", r#"try { store({ toString() { exit(); } }, "late"); } catch { text("caught"); } finally { text("finally"); }"#, ), ( "image getter", r#"try { image({ get image_url() { exit(); } }); } catch { text("caught"); } finally { text("finally"); }"#, ), ( "exception stack getter", r#"store("phase", "before formatting"); throw { get stack() { store("phase", "before"); exit(); for (;;) {} }, toString() { for (;;) {} } };"#, ), ] { let service = InProcessCodeModeSession::new(); let response = tokio::time::timeout( Duration::from_secs(5), execute( &service, ExecuteRequest { source: format!( r#"store("phase", "before"); text("before"); {source} store("phase", "after"); text("after");"# ), yield_time_ms: Some(30_000), ..execute_request("") }, ), ) .await .unwrap_or_else(|_| panic!("exit did not stop {label}")); assert_eq!( response, RuntimeResponse::Result { code_mode_host_duration: None, cell_id: cell_id("1"), content_items: vec![FunctionCallOutputContentItem::InputText { text: "before".to_string(), }], result_buffered_for: Duration::ZERO, error_text: None, }, "{label}" ); let response = execute( &service, ExecuteRequest { yield_time_ms: None, ..execute_request(r#"text(load("phase"));"#) }, ) .await; assert_eq!( response, RuntimeResponse::Result { code_mode_host_duration: None, cell_id: cell_id("2"), content_items: vec![FunctionCallOutputContentItem::InputText { text: "before".to_string(), }], result_buffered_for: Duration::ZERO, error_text: None, }, "{label}: next cell" ); } } #[tokio::test] async fn exit_after_yield_and_tool_response_finishes_successfully() { let delegate = Arc::new(ReleasableToolDelegate::default()); let service = InProcessCodeModeSession::new(); let yield_signal = CancellationToken::new(); let started = service .execute( ExecuteRequest { enabled_tools: vec![echo_tool()], source: r#" text("before yield"); await tools.echo({}); store("phase", "after tool"); text("after tool"); try { exit(); } catch { text("caught"); } finally { text("finally"); } text("after exit"); "# .to_string(), yield_time_ms: Some(30_000), ..execute_request("") }, delegate.clone(), Some(yield_signal.clone()), ) .await .unwrap(); let response = tokio::spawn(started.initial_response()); wait_until_tool_started(&delegate).await.unwrap(); yield_signal.cancel(); assert_eq!( response.await.unwrap().unwrap(), RuntimeResponse::Yielded { code_mode_host_duration: None, cell_id: cell_id("1"), content_items: vec![FunctionCallOutputContentItem::InputText { text: "before yield".to_string(), }], } ); delegate.release_tool(); let actual = service .wait( WaitRequest { max_output_tokens: None, cell_id: cell_id("1"), yield_time_ms: 30_000, }, /*preempt*/ None, ) .await .unwrap(); assert_eq!( 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, }) ); assert_eq!( execute( &service, ExecuteRequest { yield_time_ms: None, ..execute_request(r#"text(load("phase"));"#) }, ) .await, RuntimeResponse::Result { code_mode_host_duration: None, cell_id: cell_id("2"), content_items: vec![FunctionCallOutputContentItem::InputText { text: "after tool".to_string(), }], result_buffered_for: Duration::ZERO, error_text: None, } ); } #[tokio::test] async fn stored_values_are_shared_between_cells_but_not_sessions() { let first_session = InProcessCodeModeSession::new(); let second_session = InProcessCodeModeSession::new(); let write_response = execute( &first_session, ExecuteRequest { source: r#"store("key", "visible");"#.to_string(), yield_time_ms: None, ..execute_request("") }, ) .await; let same_session = execute( &first_session, ExecuteRequest { source: r#"text(String(load("key")));"#.to_string(), yield_time_ms: None, ..execute_request("") }, ) .await; let other_session = execute( &second_session, ExecuteRequest { source: r#"text(String(load("key")));"#.to_string(), yield_time_ms: None, ..execute_request("") }, ) .await; assert_eq!( write_response, RuntimeResponse::Result { code_mode_host_duration: None, cell_id: cell_id("1"), content_items: Vec::new(), result_buffered_for: Duration::ZERO, error_text: None, } ); assert_eq!( same_session, RuntimeResponse::Result { code_mode_host_duration: None, cell_id: cell_id("2"), content_items: vec![FunctionCallOutputContentItem::InputText { text: "visible".to_string(), }], result_buffered_for: Duration::ZERO, error_text: None, } ); assert_eq!( other_session, RuntimeResponse::Result { code_mode_host_duration: None, cell_id: cell_id("1"), content_items: vec![FunctionCallOutputContentItem::InputText { text: "undefined".to_string(), }], result_buffered_for: Duration::ZERO, error_text: None, } ); } #[tokio::test] async fn storing_undefined_preserves_the_previous_value() { let service = InProcessCodeModeSession::new(); let response = execute( &service, ExecuteRequest { source: r#" store("key", null); try { store("key", undefined); } catch (error) { text(String(error)); } text(load("key")); "# .to_string(), yield_time_ms: None, ..execute_request("") }, ) .await; assert_eq!( response, RuntimeResponse::Result { code_mode_host_duration: None, cell_id: cell_id("1"), content_items: vec![ FunctionCallOutputContentItem::InputText { text: "Unable to store \"key\". Only plain serializable objects can be stored." .to_string(), }, FunctionCallOutputContentItem::InputText { text: "null".to_string(), }, ], result_buffered_for: Duration::ZERO, error_text: None, } ); } #[tokio::test] async fn json_conversion_rejects_serde_markers_without_poisoning_sessions() { let first = InProcessCodeModeSession::new(); let second = InProcessCodeModeSession::new(); let source = r#" store("healthy", 42); function rejects(value, message) { let error; try { store("healthy", value); } catch (e) { error = String(e); } if (!message.test(error ?? "")) throw new Error(`expected ${message}; got ${error ?? "no error"}`); } const keys = ["$serde_json::private::RawValue", "$serde_json::private::Number"]; for (const key of keys) { for (const value of [ {[key]: "123"}, {first: 0, [key]: "null"}, [{[key]: "123"}], JSON.parse('{"\\u0024' + key.slice(1) + '":"123"}'), ]) rejects(value, /reserved JSON object key/); // Marker text in strings and longer keys must remain valid. const literal = ['{"' + key + '":"123"}', {['prefix"' + key]: key}]; store("literal", literal); if (JSON.stringify(load("literal")) !== JSON.stringify(literal)) throw "changed literal"; } for (const segments of [2, 5]) { let encoded = "0"; for (let i = 0; i < segments; i++) { encoded = "[".repeat(80) + encoded + "]".repeat(80); if (i < segments - 1) encoded = JSON.stringify({[keys[0]]: encoded}); } // Exercise Serde's depth reset without first requiring a deep V8 object. rejects({[keys[0]]: encoded}, /reserved JSON object key/); } let value = 0; for (let depth = 1; depth <= 160; depth++) { value = [value]; if (depth === 127) { try { store("boundary", value); if (JSON.stringify(load("boundary")) !== JSON.stringify(value)) throw "changed boundary"; } catch (e) { // V8 can reach its stack limit before Serde's depth limit on Windows. if (!String(e).includes("Maximum call stack size exceeded")) throw e; } } if (depth === 128 || depth === 160) rejects(value, /recursion limit exceeded|Maximum call stack size exceeded/); } const normal = {toJSON() { return [undefined, NaN, Infinity, 1.25, 1e100]; }}; store("normal", normal); if (JSON.stringify(load("normal")) !== JSON.stringify(normal)) throw "changed normal JSON"; text("ok"); store("healthy", {[keys[0]]: "null"}); "#; for (session, source, expected_text, expected_error) in [ ( &second, r#"store("healthy", 42); text(load("healthy"));"#, "42", None, ), ( &first, source, "ok", Some("failed to serialize JavaScript value: reserved JSON object key"), ), (&first, r#"text(load("healthy"));"#, "42", None), (&second, r#"text(load("healthy"));"#, "42", None), ] { let response = execute( session, ExecuteRequest { yield_time_ms: None, ..execute_request(source) }, ) .await; let RuntimeResponse::Result { content_items, error_text, .. } = response else { panic!("unexpected response: {response:?}"); }; assert_eq!( (content_items, error_text), ( vec![FunctionCallOutputContentItem::InputText { text: expected_text.to_string() }], expected_error.map(str::to_string), ) ); } } #[tokio::test] async fn shutdown_interrupts_cpu_bound_cells() { let service = InProcessCodeModeSession::new(); let cell = service .execute( ExecuteRequest { source: "while (true) {}".to_string(), ..execute_request("") }, Arc::new(NoopCodeModeSessionDelegate), /*preempt*/ None, ) .await .unwrap(); assert_eq!( cell.initial_response().await.unwrap(), RuntimeResponse::Yielded { code_mode_host_duration: None, cell_id: cell_id("1"), content_items: Vec::new(), } ); tokio::time::timeout(Duration::from_secs(1), service.shutdown()) .await .unwrap() .unwrap(); } #[tokio::test] async fn start_cell_rejects_new_cell_after_shutdown_begins() { let service = InProcessCodeModeSession::new(); service.shutdown().await.unwrap(); let error = service .execute( execute_request("text('late');"), Arc::new(NoopCodeModeSessionDelegate), /*preempt*/ None, ) .await .err() .unwrap(); assert_eq!(error, "code mode session is shutting down".to_string()); } #[tokio::test] async fn execute_to_pending_returns_completed_for_synchronous_results() { let service = InProcessCodeModeSession::new(); let response = service .execute_to_pending( ExecuteRequest { source: r#"text("done");"#.to_string(), yield_time_ms: Some(60_000), ..execute_request("") }, Arc::new(NoopCodeModeSessionDelegate), ) .await .unwrap(); assert_eq!( response, ExecuteToPendingOutcome::Completed(RuntimeResponse::Result { code_mode_host_duration: None, cell_id: cell_id("1"), content_items: vec![FunctionCallOutputContentItem::InputText { text: "done".to_string(), }], result_buffered_for: Duration::ZERO, error_text: None, }) ); } #[tokio::test] async fn execute_to_pending_returns_once_the_runtime_is_quiescent() { let service = InProcessCodeModeSession::new(); let response = tokio::time::timeout( Duration::from_secs(1), service.execute_to_pending( ExecuteRequest { source: r#"text("before"); await new Promise(() => {});"#.to_string(), yield_time_ms: Some(60_000), ..execute_request("") }, Arc::new(NoopCodeModeSessionDelegate), ), ) .await .unwrap() .unwrap(); assert_eq!( response, ExecuteToPendingOutcome::Pending { cell_id: cell_id("1"), content_items: vec![FunctionCallOutputContentItem::InputText { text: "before".to_string(), }], pending_tool_call_ids: Vec::new(), } ); let termination = service.terminate(cell_id("1").into()).await.unwrap(); assert_eq!( termination, WaitOutcome::LiveCell(RuntimeResponse::Terminated { code_mode_host_duration: None, cell_id: cell_id("1"), content_items: Vec::new(), }) ); } #[tokio::test] async fn execute_to_pending_identifies_tool_calls_in_paused_frontier() { let service = InProcessCodeModeSession::new(); let response = service .execute_to_pending( ExecuteRequest { enabled_tools: vec![echo_tool()], source: r#" await Promise.all([ tools.echo({ value: "first" }), tools.echo({ value: "second" }), ]); "# .to_string(), yield_time_ms: Some(60_000), ..execute_request("") }, Arc::new(NoopCodeModeSessionDelegate), ) .await .unwrap(); assert_eq!( response, ExecuteToPendingOutcome::Pending { cell_id: cell_id("1"), content_items: Vec::new(), pending_tool_call_ids: vec!["tool-1".to_string(), "tool-2".to_string()], } ); let termination = service.terminate(cell_id("1").into()).await.unwrap(); assert_eq!( termination, WaitOutcome::LiveCell(RuntimeResponse::Terminated { code_mode_host_duration: None, cell_id: cell_id("1"), content_items: Vec::new(), }) ); } #[tokio::test] async fn execute_to_pending_excludes_delayed_timeout_tool_calls_until_wait() { let service = InProcessCodeModeSession::new(); let initial_response = service .execute_to_pending( ExecuteRequest { enabled_tools: vec![echo_tool()], source: r#" setTimeout(() => { tools.echo({ value: "delayed" }); }, 1000); await Promise.all([ tools.echo({ value: "second" }), tools.echo({ value: "third" }), ]); "# .to_string(), yield_time_ms: Some(60_000), ..execute_request("") }, Arc::new(NoopCodeModeSessionDelegate), ) .await .unwrap(); assert_eq!( initial_response, ExecuteToPendingOutcome::Pending { cell_id: cell_id("1"), content_items: Vec::new(), pending_tool_call_ids: vec!["tool-1".to_string(), "tool-2".to_string()], } ); tokio::time::sleep(Duration::from_secs(2)).await; let resumed_response = tokio::time::timeout( Duration::from_secs(1), service.wait_to_pending(WaitToPendingRequest { cell_id: cell_id("1"), }), ) .await .unwrap() .unwrap(); assert_eq!( resumed_response, WaitToPendingOutcome::LiveCell(ExecuteToPendingOutcome::Pending { cell_id: cell_id("1"), content_items: Vec::new(), pending_tool_call_ids: vec!["tool-3".to_string()], }) ); let termination = service.terminate(cell_id("1").into()).await.unwrap(); assert_eq!( termination, WaitOutcome::LiveCell(RuntimeResponse::Terminated { code_mode_host_duration: None, cell_id: cell_id("1"), content_items: Vec::new(), }) ); } #[tokio::test] async fn wait_to_pending_returns_after_resumed_runtime_becomes_quiescent_again() { let delegate = Arc::new(ReleasableToolDelegate::default()); let service = InProcessCodeModeSession::new(); let initial_response = service .execute_to_pending( ExecuteRequest { enabled_tools: vec![echo_tool()], source: r#" await tools.echo({}); text("after"); await new Promise(() => {}); "# .to_string(), yield_time_ms: Some(60_000), ..execute_request("") }, delegate.clone(), ) .await .unwrap(); assert_eq!( initial_response, ExecuteToPendingOutcome::Pending { cell_id: cell_id("1"), content_items: Vec::new(), pending_tool_call_ids: vec!["tool-1".to_string()], } ); delegate.release_tool(); let resumed_response = tokio::time::timeout( Duration::from_secs(1), service.wait_to_pending(WaitToPendingRequest { cell_id: cell_id("1"), }), ) .await .unwrap() .unwrap(); assert_eq!( resumed_response, WaitToPendingOutcome::LiveCell(ExecuteToPendingOutcome::Pending { cell_id: cell_id("1"), content_items: vec![FunctionCallOutputContentItem::InputText { text: "after".to_string(), }], pending_tool_call_ids: Vec::new(), }) ); let termination = service.terminate(cell_id("1").into()).await.unwrap(); assert_eq!( termination, WaitOutcome::LiveCell(RuntimeResponse::Terminated { code_mode_host_duration: None, cell_id: cell_id("1"), content_items: Vec::new(), }) ); } #[tokio::test] async fn wait_to_pending_returns_completed_after_resumed_runtime_finishes() { let delegate = Arc::new(ReleasableToolDelegate::default()); let service = InProcessCodeModeSession::new(); let initial_response = service .execute_to_pending( ExecuteRequest { enabled_tools: vec![echo_tool()], source: r#" await tools.echo({}); text("done"); "# .to_string(), yield_time_ms: Some(60_000), ..execute_request("") }, delegate.clone(), ) .await .unwrap(); assert_eq!( initial_response, ExecuteToPendingOutcome::Pending { cell_id: cell_id("1"), content_items: Vec::new(), pending_tool_call_ids: vec!["tool-1".to_string()], } ); delegate.release_tool(); let resumed_response = tokio::time::timeout( Duration::from_secs(1), service.wait_to_pending(WaitToPendingRequest { cell_id: cell_id("1"), }), ) .await .unwrap() .unwrap(); assert_eq!( resumed_response, WaitToPendingOutcome::LiveCell(ExecuteToPendingOutcome::Completed( RuntimeResponse::Result { code_mode_host_duration: None, cell_id: cell_id("1"), content_items: vec![FunctionCallOutputContentItem::InputText { text: "done".to_string(), }], result_buffered_for: Duration::ZERO, error_text: None, } )) ); } #[tokio::test] async fn global_scope_contains_only_allowed_items() { let service = InProcessCodeModeSession::new(); let response = execute( &service, ExecuteRequest { enabled_tools: vec![echo_tool()], source: "text(JSON.stringify(Object.getOwnPropertyNames(globalThis).sort()));" .to_string(), yield_time_ms: None, ..execute_request("") }, ) .await; let RuntimeResponse::Result { content_items, error_text: None, .. } = response else { panic!("global scope inspection failed unexpectedly: {response:?}"); }; let [FunctionCallOutputContentItem::InputText { text }] = content_items.as_slice() else { panic!("global scope inspection returned unexpected output: {content_items:?}"); }; let globals = serde_json::from_str::>(text) .expect("global scope inspection should return a JSON array"); let expected = [ "AggregateError", "ALL_TOOLS", "Array", "ArrayBuffer", "AsyncDisposableStack", "BigInt", "BigInt64Array", "BigUint64Array", "Boolean", "clearTimeout", "DataView", "Date", "DisposableStack", "Error", "EvalError", "FinalizationRegistry", "Float16Array", "Float32Array", "Float64Array", "Function", "Infinity", "Int16Array", "Int32Array", "Int8Array", "Intl", "Iterator", "JSON", "Map", "Math", "NaN", "Number", "Object", "Promise", "Proxy", "RangeError", "ReferenceError", "Reflect", "RegExp", "Set", "String", "SuppressedError", "Symbol", "SyntaxError", "Temporal", "TypeError", "URIError", "Uint16Array", "Uint32Array", "Uint8Array", "Uint8ClampedArray", "WeakMap", "WeakRef", "WeakSet", "__codexContentItems", "add_content", "as_settled", "audio", "decodeURI", "decodeURIComponent", "encodeURI", "encodeURIComponent", "escape", "exit", "eval", "generatedImage", "globalThis", "image", "isFinite", "isNaN", "load", "notify", "parseFloat", "parseInt", "setTimeout", "store", "stream_settled", "text", "tools", "undefined", "unescape", "yield_control", ]; for global in &globals { assert!( expected.contains(&global.as_str()), "unexpected global {global} in {globals:?}" ); } } #[tokio::test] async fn v8_console_is_not_exposed_on_global_this() { let service = InProcessCodeModeSession::new(); let response = execute( &service, ExecuteRequest { source: r#"text(String(Object.hasOwn(globalThis, "console")));"#.to_string(), yield_time_ms: None, ..execute_request("") }, ) .await; assert_eq!( response, RuntimeResponse::Result { code_mode_host_duration: None, cell_id: cell_id("1"), content_items: vec![FunctionCallOutputContentItem::InputText { text: "false".to_string(), }], result_buffered_for: Duration::ZERO, error_text: None, } ); } #[tokio::test] async fn date_locale_string_formats_with_icu_data() { let service = InProcessCodeModeSession::new(); let response = execute( &service, ExecuteRequest { source: r#" const value = new Date("2025-01-02T03:04:05Z") .toLocaleString("fr-FR", { weekday: "long", month: "long", day: "numeric", hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: false, timeZone: "UTC", }); text(value); "# .to_string(), yield_time_ms: None, ..execute_request("") }, ) .await; assert_eq!( response, RuntimeResponse::Result { code_mode_host_duration: None, cell_id: cell_id("1"), content_items: vec![FunctionCallOutputContentItem::InputText { text: "jeudi 2 janvier \u{e0} 03:04:05".to_string(), }], result_buffered_for: Duration::ZERO, error_text: None, } ); } #[tokio::test] async fn intl_date_time_format_formats_with_icu_data() { let service = InProcessCodeModeSession::new(); let response = execute( &service, ExecuteRequest { source: r#" const formatter = new Intl.DateTimeFormat("fr-FR", { weekday: "long", month: "long", day: "numeric", hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: false, timeZone: "UTC", }); text(formatter.format(new Date("2025-01-02T03:04:05Z"))); "# .to_string(), yield_time_ms: None, ..execute_request("") }, ) .await; assert_eq!( response, RuntimeResponse::Result { code_mode_host_duration: None, cell_id: cell_id("1"), content_items: vec![FunctionCallOutputContentItem::InputText { text: "jeudi 2 janvier \u{e0} 03:04:05".to_string(), }], result_buffered_for: Duration::ZERO, error_text: None, } ); } #[tokio::test] async fn output_helpers_return_undefined() { let service = InProcessCodeModeSession::new(); let response = execute( &service, ExecuteRequest { source: r#" const returnsUndefined = [ text("first"), image("data:image/png;base64,AAA"), audio("data:audio/wav;base64,YXVkaW8="), notify("ping"), ].map((value) => value === undefined); text(JSON.stringify(returnsUndefined)); "# .to_string(), yield_time_ms: None, ..execute_request("") }, ) .await; assert_eq!( response, RuntimeResponse::Result { code_mode_host_duration: None, cell_id: cell_id("1"), content_items: vec![ FunctionCallOutputContentItem::InputText { text: "first".to_string(), }, FunctionCallOutputContentItem::InputImage { image_url: "data:image/png;base64,AAA".to_string(), detail: Some(crate::DEFAULT_IMAGE_DETAIL), }, FunctionCallOutputContentItem::InputAudio { audio_url: "data:audio/wav;base64,YXVkaW8=".to_string(), }, FunctionCallOutputContentItem::InputText { text: "[true,true,true,true]".to_string(), }, ], result_buffered_for: Duration::ZERO, error_text: None, } ); } #[tokio::test] async fn text_helper_serializes_objects() { let service = InProcessCodeModeSession::new(); let response = execute( &service, ExecuteRequest { source: "text({ json: true });".to_string(), yield_time_ms: None, ..execute_request("") }, ) .await; assert_eq!( response, RuntimeResponse::Result { code_mode_host_duration: None, cell_id: cell_id("1"), content_items: vec![FunctionCallOutputContentItem::InputText { text: r#"{"json":true}"#.to_string(), }], result_buffered_for: Duration::ZERO, error_text: None, } ); } #[tokio::test] 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 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(), result_buffered_for: Duration::ZERO, error_text: Some(error_text), } ); } } #[tokio::test] async fn audio_helper_accepts_audio_url_object_and_raw_mcp_audio_block() { let service = InProcessCodeModeSession::new(); let response = execute( &service, ExecuteRequest { source: r#" audio({ audio_url: "data:audio/mpeg;base64,YXVkaW8=", }); audio({ type: "audio", data: "YXVkaW8=", mimeType: "audio/wav", }); "# .to_string(), yield_time_ms: None, ..execute_request("") }, ) .await; assert_eq!( response, RuntimeResponse::Result { code_mode_host_duration: None, cell_id: cell_id("1"), content_items: vec![ FunctionCallOutputContentItem::InputAudio { audio_url: "data:audio/mpeg;base64,YXVkaW8=".to_string(), }, FunctionCallOutputContentItem::InputAudio { audio_url: "data:audio/wav;base64,YXVkaW8=".to_string(), }, ], result_buffered_for: Duration::ZERO, error_text: None, } ); } #[path = "service_audio_tests.rs"] mod audio_tests; #[tokio::test] async fn audio_helper_rejects_non_data_urls() { for source in [ r#"audio("https://example.com/audio.wav");"#, r#"audio({ audio_url: "file:///tmp/audio.wav" });"#, ] { let service = InProcessCodeModeSession::new(); let response = execute( &service, ExecuteRequest { source: source.to_string(), yield_time_ms: None, ..execute_request("") }, ) .await; assert_eq!( response, RuntimeResponse::Result { 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(), ), } ); } } #[tokio::test] async fn image_helper_accepts_raw_mcp_image_block_with_original_detail() { let service = InProcessCodeModeSession::new(); let response = execute(&service, ExecuteRequest { source: r#" image({ type: "image", data: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4nGP4z8DwHwAFAAH/iZk9HQAAAABJRU5ErkJggg==", mimeType: "image/png", _meta: { "codex/imageDetail": "original" }, }); "# .to_string(), yield_time_ms: None, ..execute_request("") }) .await; assert_eq!( response, RuntimeResponse::Result { code_mode_host_duration: None, cell_id: cell_id("1"), content_items: vec![FunctionCallOutputContentItem::InputImage { image_url: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4nGP4z8DwHwAFAAH/iZk9HQAAAABJRU5ErkJggg==".to_string(), detail: Some(crate::ImageDetail::Original), }], result_buffered_for: Duration::ZERO, error_text: None, } ); } #[tokio::test] async fn generated_image_helper_appends_image_and_output_hint() { let service = InProcessCodeModeSession::new(); let response = execute( &service, ExecuteRequest { source: r#" generatedImage({ image_url: "data:image/png;base64,AAA", output_hint: "generated image save hint", }); "# .to_string(), yield_time_ms: None, ..execute_request("") }, ) .await; assert_eq!( response, RuntimeResponse::Result { code_mode_host_duration: None, cell_id: cell_id("1"), content_items: vec![ FunctionCallOutputContentItem::InputImage { image_url: "data:image/png;base64,AAA".to_string(), detail: Some(crate::DEFAULT_IMAGE_DETAIL), }, FunctionCallOutputContentItem::InputText { text: "generated image save hint".to_string(), }, ], result_buffered_for: Duration::ZERO, error_text: None, } ); } #[tokio::test] async fn image_helper_second_arg_overrides_explicit_object_detail() { let service = InProcessCodeModeSession::new(); let response = execute( &service, ExecuteRequest { source: r#" image( { image_url: "data:image/png;base64,AAA", detail: "high", }, "original", ); "# .to_string(), yield_time_ms: None, ..execute_request("") }, ) .await; assert_eq!( response, RuntimeResponse::Result { code_mode_host_duration: None, cell_id: cell_id("1"), content_items: vec![FunctionCallOutputContentItem::InputImage { image_url: "data:image/png;base64,AAA".to_string(), detail: Some(crate::ImageDetail::Original), }], result_buffered_for: Duration::ZERO, error_text: None, } ); } #[tokio::test] async fn image_helper_second_arg_overrides_raw_mcp_image_detail() { let service = InProcessCodeModeSession::new(); let response = execute(&service, ExecuteRequest { source: r#" image( { type: "image", data: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4nGP4z8DwHwAFAAH/iZk9HQAAAABJRU5ErkJggg==", mimeType: "image/png", _meta: { "codex/imageDetail": "original" }, }, "high", ); "# .to_string(), yield_time_ms: None, ..execute_request("") }) .await; assert_eq!( response, RuntimeResponse::Result { code_mode_host_duration: None, cell_id: cell_id("1"), content_items: vec![FunctionCallOutputContentItem::InputImage { image_url: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4nGP4z8DwHwAFAAH/iZk9HQAAAABJRU5ErkJggg==".to_string(), detail: Some(crate::ImageDetail::High), }], result_buffered_for: Duration::ZERO, error_text: None, } ); } #[tokio::test] async fn image_helper_accepts_low_detail() { let service = InProcessCodeModeSession::new(); let response = execute( &service, ExecuteRequest { source: r#" image({ image_url: "data:image/png;base64,AAA", detail: "low", }); "# .to_string(), yield_time_ms: None, ..execute_request("") }, ) .await; assert_eq!( response, RuntimeResponse::Result { code_mode_host_duration: None, cell_id: cell_id("1"), content_items: vec![FunctionCallOutputContentItem::InputImage { image_url: "data:image/png;base64,AAA".to_string(), detail: Some(crate::ImageDetail::Low), }], result_buffered_for: Duration::ZERO, error_text: None, } ); } #[tokio::test] async fn image_helpers_reject_remote_urls() { for image_url in [ "http://example.com/image.jpg", "https://example.com/image.jpg", ] { for source in [ format!("image({image_url:?});"), format!("generatedImage({{ image_url: {image_url:?} }});"), ] { let service = InProcessCodeModeSession::new(); let response = execute( &service, ExecuteRequest { source, yield_time_ms: None, ..execute_request("") }, ) .await; assert_eq!( response, RuntimeResponse::Result { 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(), ), } ); } } } #[tokio::test] async fn image_helpers_reject_invalid_image_outputs() { let image_url = "Error executing tool exec: Expected at least one message to convert to CallToolResult"; for source in [ format!("image({image_url:?}, \"original\");"), format!("generatedImage({{ image_url: {image_url:?} }});"), ] { let service = InProcessCodeModeSession::new(); let response = execute( &service, ExecuteRequest { source, yield_time_ms: None, ..execute_request("") }, ) .await; assert_eq!( response, RuntimeResponse::Result { 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(), ), } ); } } #[tokio::test] async fn image_helper_rejects_unsupported_detail() { let service = InProcessCodeModeSession::new(); let response = execute( &service, ExecuteRequest { source: r#" image({ image_url: "data:image/png;base64,AAA", detail: "medium", }); "# .to_string(), yield_time_ms: None, ..execute_request("") }, ) .await; assert_eq!( response, RuntimeResponse::Result { 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()), } ); } #[tokio::test] async fn image_helper_rejects_raw_mcp_result_container() { let service = InProcessCodeModeSession::new(); let response = execute(&service, ExecuteRequest { source: r#" image({ content: [ { type: "image", data: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4nGP4z8DwHwAFAAH/iZk9HQAAAABJRU5ErkJggg==", mimeType: "image/png", _meta: { "codex/imageDetail": "original" }, }, ], isError: false, }); "# .to_string(), yield_time_ms: None, ..execute_request("") }) .await; assert_eq!( response, RuntimeResponse::Result { 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(), ), } ); } #[tokio::test] async fn wait_reports_missing_cell_separately_from_runtime_results() { let service = InProcessCodeModeSession::new(); let response = service .wait( WaitRequest { cell_id: cell_id("missing"), yield_time_ms: 1, max_output_tokens: None, }, /*preempt*/ None, ) .await .unwrap(); assert_eq!( response, WaitOutcome::MissingCell(RuntimeResponse::Result { 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()), }) ); }