use codex_code_mode_protocol::NoopCodeModeSessionDelegate; use std::sync::Arc; use std::sync::atomic::AtomicBool; use std::sync::atomic::Ordering; 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::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_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 execute_waits_for_nested_tool_during_yield_grace() { 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()).await.unwrap(); let response = tokio::spawn(started.initial_response()); wait_until_tool_started(&delegate).await; tokio::time::advance(Duration::from_millis(10_500)).await; delegate.release_tool(); wait_until_finished(&response).await; 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(), }], error_text: None, } ); } #[tokio::test(start_paused = true)] async fn execute_and_wait_clamp_yield_grace_without_stopping_the_cell() { 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(), ) .await .unwrap(); let initial_response = tokio::spawn(started.initial_response()); wait_until_tool_started(&delegate).await; tokio::time::advance(Duration::from_millis(9_999)).await; assert!(!initial_response.is_finished()); tokio::time::advance(Duration::from_millis(/*millis*/ 1)).await; wait_until_finished(&initial_response).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, }) .await; let wait_response = tokio::spawn(wait_response); tokio::task::yield_now().await; tokio::time::advance(Duration::from_secs(/*secs*/ 10)).await; wait_until_finished(&wait_response).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, }) .await; let completion = tokio::spawn(completion); wait_until_finished(&completion).await; 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(), }], error_text: None, }) ); } #[tokio::test(start_paused = true)] async fn wait_waits_for_nested_tool_during_yield_grace() { 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, }) .await; let response = tokio::spawn(response); tokio::task::yield_now().await; tokio::time::advance(Duration::from_millis(10_500)).await; delegate.release_tool(); wait_until_finished(&response).await; 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(), }], error_text: None, }) ); } #[tokio::test(start_paused = true)] async fn zero_yield_limit_is_immediate_and_scoped_to_its_session() { 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()) .await .unwrap(); let limited_started = limited_session .execute(request, limited_delegate.clone()) .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; wait_until_tool_started(&limited_delegate).await; wait_until_finished(&zero_response).await; 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, }) .await; let zero_wait = tokio::spawn(zero_wait); wait_until_finished(&zero_wait).await; 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; wait_until_finished(&limited_response).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(); } async fn wait_until_finished(task: &tokio::task::JoinHandle) { for _ in 0..10_000 { if task.is_finished() { return; } tokio::task::yield_now().await; } panic!("code-mode response did not finish while virtual time was held in the grace period"); } async fn wait_until_tool_started(delegate: &ReleasableToolDelegate) { for _ in 0..10_000 { if delegate.tool_started.load(Ordering::Acquire) { return; } tokio::task::yield_now().await; } panic!("nested code-mode tool did not start"); } #[derive(Default)] struct ReleasableToolDelegate { tool_release: Notify, tool_started: AtomicBool, } 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.store(true, Ordering::Release); 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) {} } 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, output_schema: None, } } async fn execute(service: &InProcessCodeModeSession, request: ExecuteRequest) -> RuntimeResponse { service .execute(request, Arc::new(NoopCodeModeSessionDelegate)) .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(), }], 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(), 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(), }], 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(), }], 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(), }, ], error_text: None, } ); } #[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), ) .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), ) .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(), }], 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")).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")).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")).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")).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(), }], 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", "audio", "decodeURI", "decodeURIComponent", "encodeURI", "encodeURIComponent", "escape", "exit", "eval", "generatedImage", "globalThis", "image", "isFinite", "isNaN", "load", "notify", "parseFloat", "parseInt", "setTimeout", "store", "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(), }], 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(), }], 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(), }], 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(), }, ], 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(), }], error_text: None, } ); } #[tokio::test] async fn text_helper_surfaces_stringify_errors() { let service = InProcessCodeModeSession::new(); 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(), 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(), }, ], 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(), 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), }], 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(), }, ], 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), }], 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), }], 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), }], 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(), 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(), 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(), 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(), 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, }) .await .unwrap(); assert_eq!( response, WaitOutcome::MissingCell(RuntimeResponse::Result { code_mode_host_duration: None, cell_id: cell_id("missing"), content_items: Vec::new(), error_text: Some("exec cell missing not found".to_string()), }) ); }