Download codex-rs/code-mode-runtime/src/service_tests.rs from SaylorTwift/codex: direct link, hf CLI and curl.
- Browser
- Download file 46.1 kB
-
https://huggingface.co/SaylorTwift/codex/resolve/main/codex-rs/code-mode-runtime/src/service_tests.rs
- Command line
-
hf download hf://SaylorTwift/codex/codex-rs/code-mode-runtime/src/service_tests.rs
-
curl -L -o service_tests.rs https://huggingface.co/SaylorTwift/codex/resolve/main/codex-rs/code-mode-runtime/src/service_tests.rs
46.1 kB
| 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; | |
| 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:?}" | |
| ); | |
| } | |
| } | |
| 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, | |
| } | |
| ); | |
| } | |
| 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, | |
| }) | |
| ); | |
| } | |
| 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, | |
| }) | |
| ); | |
| } | |
| 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<T>(task: &tokio::task::JoinHandle<T>) { | |
| 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"); | |
| } | |
| 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() | |
| } | |
| 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, | |
| } | |
| ); | |
| } | |
| 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, | |
| } | |
| ); | |
| } | |
| 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, | |
| } | |
| ); | |
| } | |
| 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(); | |
| } | |
| 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()); | |
| } | |
| 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, | |
| }) | |
| ); | |
| } | |
| 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(), | |
| }) | |
| ); | |
| } | |
| 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(), | |
| }) | |
| ); | |
| } | |
| 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(), | |
| }) | |
| ); | |
| } | |
| 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(), | |
| }) | |
| ); | |
| } | |
| 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, | |
| } | |
| )) | |
| ); | |
| } | |
| 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::<Vec<String>>(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:?}" | |
| ); | |
| } | |
| } | |
| 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, | |
| } | |
| ); | |
| } | |
| 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, | |
| } | |
| ); | |
| } | |
| 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, | |
| } | |
| ); | |
| } | |
| 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, | |
| } | |
| ); | |
| } | |
| 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, | |
| } | |
| ); | |
| } | |
| 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), | |
| } | |
| ); | |
| } | |
| 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, | |
| } | |
| ); | |
| } | |
| mod audio_tests; | |
| 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(), | |
| ), | |
| } | |
| ); | |
| } | |
| } | |
| 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, | |
| } | |
| ); | |
| } | |
| 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, | |
| } | |
| ); | |
| } | |
| 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, | |
| } | |
| ); | |
| } | |
| 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, | |
| } | |
| ); | |
| } | |
| 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, | |
| } | |
| ); | |
| } | |
| 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(), | |
| ), | |
| } | |
| ); | |
| } | |
| } | |
| } | |
| 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(), | |
| ), | |
| } | |
| ); | |
| } | |
| } | |
| 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()), | |
| } | |
| ); | |
| } | |
| 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(), | |
| ), | |
| } | |
| ); | |
| } | |
| 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()), | |
| }) | |
| ); | |
| } | |