diff --git a/codex-rs/app-server-client/src/lib.rs b/codex-rs/app-server-client/src/lib.rs index 73083322f..fef32574f 100644 --- a/codex-rs/app-server-client/src/lib.rs +++ b/codex-rs/app-server-client/src/lib.rs @@ -1888,6 +1888,7 @@ mod tests { is_secret: false, options: Some(vec![]), }], + auto_resolution_ms: None, }) .expect("params should serialize"), ), @@ -1949,6 +1950,7 @@ mod tests { is_secret: false, options: Some(vec![]), }], + auto_resolution_ms: None, }) .expect("params should serialize"), ), diff --git a/codex-rs/app-server-protocol/schema/json/ServerRequest.json b/codex-rs/app-server-protocol/schema/json/ServerRequest.json index dbfca64f4..5ab8d7c63 100644 --- a/codex-rs/app-server-protocol/schema/json/ServerRequest.json +++ b/codex-rs/app-server-protocol/schema/json/ServerRequest.json @@ -1690,6 +1690,15 @@ "ToolRequestUserInputParams": { "description": "EXPERIMENTAL. Params sent with a request_user_input event.", "properties": { + "autoResolutionMs": { + "default": null, + "format": "uint64", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + }, "itemId": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/ToolRequestUserInputParams.json b/codex-rs/app-server-protocol/schema/json/ToolRequestUserInputParams.json index 153d3bad6..947371fd1 100644 --- a/codex-rs/app-server-protocol/schema/json/ToolRequestUserInputParams.json +++ b/codex-rs/app-server-protocol/schema/json/ToolRequestUserInputParams.json @@ -57,6 +57,15 @@ }, "description": "EXPERIMENTAL. Params sent with a request_user_input event.", "properties": { + "autoResolutionMs": { + "default": null, + "format": "uint64", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + }, "itemId": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json index fadec6acb..9aedb8a7e 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -5626,6 +5626,15 @@ "$schema": "http://json-schema.org/draft-07/schema#", "description": "EXPERIMENTAL. Params sent with a request_user_input event.", "properties": { + "autoResolutionMs": { + "default": null, + "format": "uint64", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + }, "itemId": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ToolRequestUserInputParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ToolRequestUserInputParams.ts index bee81cb8e..73ff8b678 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/ToolRequestUserInputParams.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ToolRequestUserInputParams.ts @@ -6,4 +6,4 @@ import type { ToolRequestUserInputQuestion } from "./ToolRequestUserInputQuestio /** * EXPERIMENTAL. Params sent with a request_user_input event. */ -export type ToolRequestUserInputParams = { threadId: string, turnId: string, itemId: string, questions: Array, }; +export type ToolRequestUserInputParams = { threadId: string, turnId: string, itemId: string, questions: Array, autoResolutionMs: number | null, }; diff --git a/codex-rs/app-server-protocol/src/protocol/v2/item.rs b/codex-rs/app-server-protocol/src/protocol/v2/item.rs index 29d212ab1..f556890a9 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2/item.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2/item.rs @@ -1464,6 +1464,9 @@ pub struct ToolRequestUserInputParams { pub turn_id: String, pub item_id: String, pub questions: Vec, + #[serde(default)] + #[ts(type = "number | null")] + pub auto_resolution_ms: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] diff --git a/codex-rs/app-server/src/bespoke_event_handling.rs b/codex-rs/app-server/src/bespoke_event_handling.rs index 1f47bc4dd..d1099d3b1 100644 --- a/codex-rs/app-server/src/bespoke_event_handling.rs +++ b/codex-rs/app-server/src/bespoke_event_handling.rs @@ -687,6 +687,7 @@ pub(crate) async fn apply_bespoke_event_handling( turn_id: request.turn_id, item_id: request.call_id, questions, + auto_resolution_ms: request.auto_resolution_ms, }; let (pending_request_id, rx) = outgoing .send_request(ServerRequestPayload::ToolRequestUserInput(params)) diff --git a/codex-rs/app-server/src/outgoing_message.rs b/codex-rs/app-server/src/outgoing_message.rs index ba75d0afd..52d74b819 100644 --- a/codex-rs/app-server/src/outgoing_message.rs +++ b/codex-rs/app-server/src/outgoing_message.rs @@ -1259,6 +1259,7 @@ mod tests { turn_id: "turn-1".to_string(), item_id: "call-1".to_string(), questions: vec![], + auto_resolution_ms: None, }, )) .await; @@ -1321,6 +1322,7 @@ mod tests { turn_id: "turn-1".to_string(), item_id: "call-1".to_string(), questions: vec![], + auto_resolution_ms: None, }, )) .await; diff --git a/codex-rs/app-server/src/request_processors/thread_processor_tests.rs b/codex-rs/app-server/src/request_processors/thread_processor_tests.rs index 7952517c9..cc93600f2 100644 --- a/codex-rs/app-server/src/request_processors/thread_processor_tests.rs +++ b/codex-rs/app-server/src/request_processors/thread_processor_tests.rs @@ -1140,6 +1140,7 @@ mod thread_processor_behavior_tests { turn_id: "turn-1".to_string(), item_id: "call-1".to_string(), questions: vec![], + auto_resolution_ms: None, }, )) .await; diff --git a/codex-rs/app-server/tests/suite/v2/request_user_input.rs b/codex-rs/app-server/tests/suite/v2/request_user_input.rs index da61d3411..b4a764ddb 100644 --- a/codex-rs/app-server/tests/suite/v2/request_user_input.rs +++ b/codex-rs/app-server/tests/suite/v2/request_user_input.rs @@ -2,7 +2,6 @@ use anyhow::Result; use app_test_support::TestAppServer; use app_test_support::create_final_assistant_message_sse_response; use app_test_support::create_mock_responses_server_sequence; -use app_test_support::create_request_user_input_sse_response; use app_test_support::to_response; use codex_app_server_protocol::JSONRPCMessage; use codex_app_server_protocol::JSONRPCResponse; @@ -18,15 +17,46 @@ use codex_protocol::config_types::CollaborationMode; use codex_protocol::config_types::ModeKind; use codex_protocol::config_types::Settings; use codex_protocol::openai_models::ReasoningEffort; +use core_test_support::responses; +use serde_json::json; use tokio::time::timeout; const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10); +fn create_request_user_input_sse_response_with_auto_resolution( + call_id: &str, + auto_resolution_ms: u64, +) -> anyhow::Result { + let tool_call_arguments = serde_json::to_string(&json!({ + "questions": [{ + "id": "confirm_path", + "header": "Confirm", + "question": "Proceed with the plan?", + "options": [{ + "label": "Yes (Recommended)", + "description": "Continue the current plan." + }, { + "label": "No", + "description": "Stop and revisit the approach." + }] + }], + "autoResolutionMs": auto_resolution_ms + }))?; + + Ok(responses::sse(vec![ + responses::ev_response_created("resp-1"), + responses::ev_function_call(call_id, "request_user_input", &tool_call_arguments), + responses::ev_completed("resp-1"), + ])) +} + #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn request_user_input_round_trip() -> Result<()> { let codex_home = tempfile::TempDir::new()?; let responses = vec![ - create_request_user_input_sse_response("call1")?, + create_request_user_input_sse_response_with_auto_resolution( + "call1", /*auto_resolution_ms*/ 60_000, + )?, create_final_assistant_message_sse_response("done")?, ]; let server = create_mock_responses_server_sequence(responses).await; @@ -89,6 +119,7 @@ async fn request_user_input_round_trip() -> Result<()> { assert_eq!(params.turn_id, turn.id); assert_eq!(params.item_id, "call1"); assert_eq!(params.questions.len(), 1); + assert_eq!(params.auto_resolution_ms, Some(60_000)); let resolved_request_id = request_id.clone(); mcp.send_response( @@ -128,7 +159,6 @@ async fn request_user_input_round_trip() -> Result<()> { Ok(()) } - fn create_config_toml(codex_home: &std::path::Path, server_uri: &str) -> std::io::Result<()> { let config_toml = codex_home.join("config.toml"); std::fs::write( diff --git a/codex-rs/core/src/codex_delegate.rs b/codex-rs/core/src/codex_delegate.rs index 6ee032846..135909bf0 100644 --- a/codex-rs/core/src/codex_delegate.rs +++ b/codex-rs/core/src/codex_delegate.rs @@ -655,6 +655,7 @@ async fn handle_request_user_input( let args = RequestUserInputArgs { questions: event.questions, + auto_resolution_ms: event.auto_resolution_ms, }; let response_fut = parent_session.request_user_input(parent_ctx, parent_ctx.sub_id.clone(), args); diff --git a/codex-rs/core/src/codex_delegate_tests.rs b/codex-rs/core/src/codex_delegate_tests.rs index cb7767a16..c881823d8 100644 --- a/codex-rs/core/src/codex_delegate_tests.rs +++ b/codex-rs/core/src/codex_delegate_tests.rs @@ -433,6 +433,7 @@ async fn delegated_mcp_guardian_abort_returns_synthetic_decline_answer() { is_secret: false, options: None, }], + auto_resolution_ms: None, }, &cancel_token, ) @@ -476,6 +477,7 @@ async fn delegated_mcp_user_reviewer_returns_none_without_metadata() { is_secret: false, options: None, }], + auto_resolution_ms: None, }; let response = maybe_auto_review_mcp_request_user_input( &parent_session, diff --git a/codex-rs/core/src/mcp_skill_dependencies.rs b/codex-rs/core/src/mcp_skill_dependencies.rs index 304deb500..60915ccea 100644 --- a/codex-rs/core/src/mcp_skill_dependencies.rs +++ b/codex-rs/core/src/mcp_skill_dependencies.rs @@ -254,6 +254,7 @@ async fn should_install_mcp_dependencies( }; let args = RequestUserInputArgs { questions: vec![question], + auto_resolution_ms: None, }; let sub_id = &turn_context.sub_id; let call_id = format!("mcp-deps-{sub_id}"); diff --git a/codex-rs/core/src/mcp_tool_call.rs b/codex-rs/core/src/mcp_tool_call.rs index e5273a666..e3f7d5faf 100644 --- a/codex-rs/core/src/mcp_tool_call.rs +++ b/codex-rs/core/src/mcp_tool_call.rs @@ -1297,6 +1297,7 @@ async fn maybe_request_mcp_tool_approval( let args = RequestUserInputArgs { questions: vec![question], + auto_resolution_ms: None, }; let response = sess .request_user_input(turn_context.as_ref(), call_id.to_string(), args) diff --git a/codex-rs/core/src/session/mod.rs b/codex-rs/core/src/session/mod.rs index 08c691b9c..5e93352fd 100644 --- a/codex-rs/core/src/session/mod.rs +++ b/codex-rs/core/src/session/mod.rs @@ -2391,6 +2391,7 @@ impl Session { call_id, turn_id: turn_context.sub_id.clone(), questions: args.questions, + auto_resolution_ms: args.auto_resolution_ms, }); turn_context .turn_metadata_state diff --git a/codex-rs/core/src/tools/handlers/request_user_input_spec.rs b/codex-rs/core/src/tools/handlers/request_user_input_spec.rs index 3ba7d9e4c..0eba5eefd 100644 --- a/codex-rs/core/src/tools/handlers/request_user_input_spec.rs +++ b/codex-rs/core/src/tools/handlers/request_user_input_spec.rs @@ -6,6 +6,8 @@ use codex_tools::ToolSpec; use std::collections::BTreeMap; pub const REQUEST_USER_INPUT_TOOL_NAME: &str = "request_user_input"; +pub const MIN_AUTO_RESOLUTION_MS: u64 = 60_000; +pub const MAX_AUTO_RESOLUTION_MS: u64 = 240_000; pub fn create_request_user_input_tool(description: String) -> ToolSpec { let option_props = BTreeMap::from([ @@ -66,7 +68,14 @@ pub fn create_request_user_input_tool(description: String) -> ToolSpec { Some("Questions to show the user. Prefer 1 and do not exceed 3".to_string()), ); - let properties = BTreeMap::from([("questions".to_string(), questions_schema)]); + let auto_resolution_ms_schema = JsonSchema::number(Some(format!( + "Optional auto-resolution window in milliseconds, from {MIN_AUTO_RESOLUTION_MS} to {MAX_AUTO_RESOLUTION_MS}. Include this only when the question is useful but non-blocking and continuing with best judgment is acceptable if the user does not answer; omit it when explicit user input is required before continuing. Use {MIN_AUTO_RESOLUTION_MS} for lightly helpful context and up to {MAX_AUTO_RESOLUTION_MS} when the answer would materially unblock better work." + ))); + + let properties = BTreeMap::from([ + ("questions".to_string(), questions_schema), + ("autoResolutionMs".to_string(), auto_resolution_ms_schema), + ]); ToolSpec::Function(ResponsesApiTool { name: REQUEST_USER_INPUT_TOOL_NAME.to_string(), @@ -111,13 +120,26 @@ pub fn normalize_request_user_input_args( question.is_other = true; } + if let Some(auto_resolution_ms) = args.auto_resolution_ms { + let clamped_auto_resolution_ms = + auto_resolution_ms.clamp(MIN_AUTO_RESOLUTION_MS, MAX_AUTO_RESOLUTION_MS); + if clamped_auto_resolution_ms != auto_resolution_ms { + tracing::warn!( + auto_resolution_ms, + clamped_auto_resolution_ms, + "clamped request_user_input autoResolutionMs to supported range" + ); + args.auto_resolution_ms = Some(clamped_auto_resolution_ms); + } + } + Ok(args) } pub fn request_user_input_tool_description(available_modes: &[ModeKind]) -> String { let allowed_modes = format_allowed_modes(available_modes); format!( - "Request user input for one to three short questions and wait for the response. This tool is only available in {allowed_modes}." + "Request user input for one to three short questions and wait for the response. Set autoResolutionMs, from {MIN_AUTO_RESOLUTION_MS} to {MAX_AUTO_RESOLUTION_MS} milliseconds, only when the question is useful but non-blocking and continuing with best judgment is acceptable if the user does not answer; omit it when explicit user input is required. This tool is only available in {allowed_modes}." ) } diff --git a/codex-rs/core/src/tools/handlers/request_user_input_spec_tests.rs b/codex-rs/core/src/tools/handlers/request_user_input_spec_tests.rs index 8e6214722..2b532cd97 100644 --- a/codex-rs/core/src/tools/handlers/request_user_input_spec_tests.rs +++ b/codex-rs/core/src/tools/handlers/request_user_input_spec_tests.rs @@ -2,6 +2,8 @@ use super::*; use codex_features::Feature; use codex_features::Features; use codex_protocol::config_types::ModeKind; +use codex_protocol::request_user_input::RequestUserInputQuestion; +use codex_protocol::request_user_input::RequestUserInputQuestionOption; use codex_tools::JsonSchema; use codex_tools::request_user_input_available_modes; use pretty_assertions::assert_eq; @@ -26,7 +28,15 @@ fn request_user_input_tool_includes_questions_schema() { description: "Ask the user to choose.".to_string(), strict: false, defer_loading: None, - parameters: JsonSchema::object(BTreeMap::from([( + parameters: JsonSchema::object(BTreeMap::from([ + ( + "autoResolutionMs".to_string(), + JsonSchema::number(Some( + "Optional auto-resolution window in milliseconds, from 60000 to 240000. Include this only when the question is useful but non-blocking and continuing with best judgment is acceptable if the user does not answer; omit it when explicit user input is required before continuing. Use 60000 for lightly helpful context and up to 240000 when the answer would materially unblock better work." + .to_string(), + )), + ), + ( "questions".to_string(), JsonSchema::array( JsonSchema::object( @@ -96,12 +106,97 @@ fn request_user_input_tool_includes_questions_schema() { "Questions to show the user. Prefer 1 and do not exceed 3".to_string(), ), ), - )]), Some(vec!["questions".to_string()]), Some(false.into())), + ), + ]), Some(vec!["questions".to_string()]), Some(false.into())), output_schema: None, }) ); } +#[test] +fn normalize_request_user_input_args_clamps_out_of_range_auto_resolution_ms() { + let args = RequestUserInputArgs { + questions: vec![RequestUserInputQuestion { + id: "confirm".to_string(), + header: "Confirm".to_string(), + question: "Proceed?".to_string(), + is_other: false, + is_secret: false, + options: Some(vec![RequestUserInputQuestionOption { + label: "Yes (Recommended)".to_string(), + description: "Continue.".to_string(), + }]), + }], + auto_resolution_ms: Some(MIN_AUTO_RESOLUTION_MS - 1), + }; + + assert_eq!( + normalize_request_user_input_args(args.clone()), + Ok(RequestUserInputArgs { + questions: vec![RequestUserInputQuestion { + is_other: true, + ..args.questions[0].clone() + }], + auto_resolution_ms: Some(MIN_AUTO_RESOLUTION_MS), + }) + ); + assert_eq!( + normalize_request_user_input_args(RequestUserInputArgs { + auto_resolution_ms: Some(MAX_AUTO_RESOLUTION_MS + 1), + ..args.clone() + }), + Ok(RequestUserInputArgs { + questions: vec![RequestUserInputQuestion { + is_other: true, + ..args.questions[0].clone() + }], + auto_resolution_ms: Some(MAX_AUTO_RESOLUTION_MS), + }) + ); +} + +#[test] +fn normalize_request_user_input_args_accepts_auto_resolution_boundaries() { + let args = RequestUserInputArgs { + questions: vec![RequestUserInputQuestion { + id: "confirm".to_string(), + header: "Confirm".to_string(), + question: "Proceed?".to_string(), + is_other: false, + is_secret: false, + options: Some(vec![RequestUserInputQuestionOption { + label: "Yes (Recommended)".to_string(), + description: "Continue.".to_string(), + }]), + }], + auto_resolution_ms: Some(MIN_AUTO_RESOLUTION_MS), + }; + + assert_eq!( + normalize_request_user_input_args(args.clone()), + Ok(RequestUserInputArgs { + questions: vec![RequestUserInputQuestion { + is_other: true, + ..args.questions[0].clone() + }], + auto_resolution_ms: Some(MIN_AUTO_RESOLUTION_MS), + }) + ); + assert_eq!( + normalize_request_user_input_args(RequestUserInputArgs { + auto_resolution_ms: Some(MAX_AUTO_RESOLUTION_MS), + ..args.clone() + }), + Ok(RequestUserInputArgs { + questions: vec![RequestUserInputQuestion { + is_other: true, + ..args.questions[0].clone() + }], + auto_resolution_ms: Some(MAX_AUTO_RESOLUTION_MS), + }) + ); +} + #[test] fn request_user_input_unavailable_messages_respect_default_mode_feature_flag() { assert_eq!( @@ -136,10 +231,10 @@ fn request_user_input_unavailable_messages_respect_default_mode_feature_flag() { fn request_user_input_tool_description_mentions_available_modes() { assert_eq!( request_user_input_tool_description(&default_available_modes()), - "Request user input for one to three short questions and wait for the response. This tool is only available in Plan mode.".to_string() + "Request user input for one to three short questions and wait for the response. Set autoResolutionMs, from 60000 to 240000 milliseconds, only when the question is useful but non-blocking and continuing with best judgment is acceptable if the user does not answer; omit it when explicit user input is required. This tool is only available in Plan mode.".to_string() ); assert_eq!( request_user_input_tool_description(&default_mode_enabled_available_modes()), - "Request user input for one to three short questions and wait for the response. This tool is only available in Default or Plan mode.".to_string() + "Request user input for one to three short questions and wait for the response. Set autoResolutionMs, from 60000 to 240000 milliseconds, only when the question is useful but non-blocking and continuing with best judgment is acceptable if the user does not answer; omit it when explicit user input is required. This tool is only available in Default or Plan mode.".to_string() ); } diff --git a/codex-rs/core/tests/suite/request_user_input.rs b/codex-rs/core/tests/suite/request_user_input.rs index 902c7a8bb..88be80c88 100644 --- a/codex-rs/core/tests/suite/request_user_input.rs +++ b/codex-rs/core/tests/suite/request_user_input.rs @@ -76,10 +76,18 @@ fn call_output_content_and_success( #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn request_user_input_round_trip_resolves_pending() -> anyhow::Result<()> { - request_user_input_round_trip_for_mode(ModeKind::Plan).await + request_user_input_round_trip_for_mode(ModeKind::Plan, /*auto_resolution_ms*/ None).await } -async fn request_user_input_round_trip_for_mode(mode: ModeKind) -> anyhow::Result<()> { +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn request_user_input_round_trip_emits_auto_resolution_ms() -> anyhow::Result<()> { + request_user_input_round_trip_for_mode(ModeKind::Plan, Some(60_000)).await +} + +async fn request_user_input_round_trip_for_mode( + mode: ModeKind, + auto_resolution_ms: Option, +) -> anyhow::Result<()> { skip_if_no_network!(Ok(())); let server = start_mock_server().await; @@ -104,7 +112,7 @@ async fn request_user_input_round_trip_for_mode(mode: ModeKind) -> anyhow::Resul .await?; let call_id = "user-input-call"; - let request_args = json!({ + let mut request_args = json!({ "questions": [{ "id": "confirm_path", "header": "Confirm", @@ -117,8 +125,14 @@ async fn request_user_input_round_trip_for_mode(mode: ModeKind) -> anyhow::Resul "description": "Stop and revisit the approach." }] }] - }) - .to_string(); + }); + if let Some(auto_resolution_ms) = auto_resolution_ms { + let Some(request_args) = request_args.as_object_mut() else { + panic!("request_user_input args should be a JSON object"); + }; + request_args.insert("autoResolutionMs".to_string(), json!(auto_resolution_ms)); + } + let request_args = request_args.to_string(); let first_response = sse(vec![ ev_response_created("resp-1"), @@ -171,6 +185,7 @@ async fn request_user_input_round_trip_for_mode(mode: ModeKind) -> anyhow::Resul .await; assert_eq!(request.call_id, call_id); assert_eq!(request.questions.len(), 1); + assert_eq!(request.auto_resolution_ms, auto_resolution_ms); assert_eq!(request.questions[0].is_other, true); assert!( timeout(Duration::from_millis(200), async { @@ -445,7 +460,7 @@ async fn request_user_input_rejected_in_default_mode_by_default() -> anyhow::Res #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn request_user_input_round_trip_in_default_mode_with_feature() -> anyhow::Result<()> { - request_user_input_round_trip_for_mode(ModeKind::Default).await + request_user_input_round_trip_for_mode(ModeKind::Default, /*auto_resolution_ms*/ None).await } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] diff --git a/codex-rs/protocol/src/request_user_input.rs b/codex-rs/protocol/src/request_user_input.rs index cb076264d..dd52c5e6f 100644 --- a/codex-rs/protocol/src/request_user_input.rs +++ b/codex-rs/protocol/src/request_user_input.rs @@ -31,6 +31,9 @@ pub struct RequestUserInputQuestion { #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, JsonSchema, TS)] pub struct RequestUserInputArgs { pub questions: Vec, + #[serde(rename = "autoResolutionMs", skip_serializing_if = "Option::is_none")] + #[schemars(rename = "autoResolutionMs")] + pub auto_resolution_ms: Option, } #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, JsonSchema, TS)] @@ -52,4 +55,7 @@ pub struct RequestUserInputEvent { #[serde(default)] pub turn_id: String, pub questions: Vec, + #[serde(rename = "autoResolutionMs", skip_serializing_if = "Option::is_none")] + #[schemars(rename = "autoResolutionMs")] + pub auto_resolution_ms: Option, } diff --git a/codex-rs/tui/src/app/app_server_requests.rs b/codex-rs/tui/src/app/app_server_requests.rs index c2580bd07..479512e56 100644 --- a/codex-rs/tui/src/app/app_server_requests.rs +++ b/codex-rs/tui/src/app/app_server_requests.rs @@ -509,6 +509,7 @@ mod tests { turn_id: "turn-2".to_string(), item_id: "tool-1".to_string(), questions: Vec::new(), + auto_resolution_ms: None, }, }), None @@ -798,6 +799,7 @@ mod tests { turn_id: "turn-1".to_string(), item_id: "tool-1".to_string(), questions: Vec::new(), + auto_resolution_ms: None, }, }); @@ -820,6 +822,7 @@ mod tests { turn_id: "turn-1".to_string(), item_id: item_id.to_string(), questions: Vec::new(), + auto_resolution_ms: None, }, }); } diff --git a/codex-rs/tui/src/app/pending_interactive_replay.rs b/codex-rs/tui/src/app/pending_interactive_replay.rs index 1a21d4df5..4f90dbe9c 100644 --- a/codex-rs/tui/src/app/pending_interactive_replay.rs +++ b/codex-rs/tui/src/app/pending_interactive_replay.rs @@ -597,6 +597,7 @@ mod tests { turn_id: turn_id.to_string(), item_id: call_id.to_string(), questions: Vec::new(), + auto_resolution_ms: None, }, } } diff --git a/codex-rs/tui/src/app/tests.rs b/codex-rs/tui/src/app/tests.rs index 25c6f21d3..c7462776d 100644 --- a/codex-rs/tui/src/app/tests.rs +++ b/codex-rs/tui/src/app/tests.rs @@ -4586,6 +4586,7 @@ fn request_user_input_request(thread_id: ThreadId, turn_id: &str, item_id: &str) turn_id: turn_id.to_string(), item_id: item_id.to_string(), questions: Vec::new(), + auto_resolution_ms: None, }, } } diff --git a/codex-rs/tui/src/bottom_pane/request_user_input/mod.rs b/codex-rs/tui/src/bottom_pane/request_user_input/mod.rs index 1657ef3f4..02a807423 100644 --- a/codex-rs/tui/src/bottom_pane/request_user_input/mod.rs +++ b/codex-rs/tui/src/bottom_pane/request_user_input/mod.rs @@ -1538,6 +1538,7 @@ mod tests { item_id: "call-1".to_string(), turn_id: turn_id.to_string(), questions, + auto_resolution_ms: None, } } @@ -1600,12 +1601,14 @@ mod tests { item_id: "call-2".to_string(), turn_id: "turn-2".to_string(), questions: vec![question_with_options("q2", "Second")], + auto_resolution_ms: None, }); overlay.try_consume_user_input_request(ToolRequestUserInputParams { thread_id: "thread-1".to_string(), item_id: "call-3".to_string(), turn_id: "turn-3".to_string(), questions: vec![question_with_options("q3", "Third")], + auto_resolution_ms: None, }); overlay.handle_key_event(KeyEvent::from(KeyCode::Esc)); @@ -1623,6 +1626,7 @@ mod tests { item_id: "call-1".to_string(), turn_id: "turn-1".to_string(), questions: vec![question_with_options("q1", "First")], + auto_resolution_ms: None, }, tx, /*has_input_focus*/ true, @@ -1651,6 +1655,7 @@ mod tests { item_id: "call-1".to_string(), turn_id: "turn-1".to_string(), questions: vec![question_with_options("q1", "First")], + auto_resolution_ms: None, }, tx, /*has_input_focus*/ true, @@ -1662,6 +1667,7 @@ mod tests { item_id: "call-2".to_string(), turn_id: "turn-1".to_string(), questions: vec![question_with_options("q2", "Second")], + auto_resolution_ms: None, }); assert!( @@ -1689,6 +1695,7 @@ mod tests { item_id: "call-1".to_string(), turn_id: "turn-1".to_string(), questions: vec![question_with_options("q1", "First")], + auto_resolution_ms: None, }, tx, /*has_input_focus*/ true, @@ -1700,12 +1707,14 @@ mod tests { item_id: "call-2".to_string(), turn_id: "turn-1".to_string(), questions: vec![question_with_options("q2", "Second")], + auto_resolution_ms: None, }); overlay.try_consume_user_input_request(ToolRequestUserInputParams { thread_id: "thread-1".to_string(), item_id: "call-3".to_string(), turn_id: "turn-1".to_string(), questions: vec![question_with_options("q3", "Third")], + auto_resolution_ms: None, }); assert!( diff --git a/codex-rs/tui/src/chatwidget/interrupts.rs b/codex-rs/tui/src/chatwidget/interrupts.rs index 0b8dec2f2..301f91841 100644 --- a/codex-rs/tui/src/chatwidget/interrupts.rs +++ b/codex-rs/tui/src/chatwidget/interrupts.rs @@ -152,6 +152,7 @@ mod tests { item_id: call_id.to_string(), turn_id: turn_id.to_string(), questions: Vec::new(), + auto_resolution_ms: None, } } diff --git a/codex-rs/tui/src/chatwidget/tests/plan_mode.rs b/codex-rs/tui/src/chatwidget/tests/plan_mode.rs index 984c71d1a..830f4259b 100644 --- a/codex-rs/tui/src/chatwidget/tests/plan_mode.rs +++ b/codex-rs/tui/src/chatwidget/tests/plan_mode.rs @@ -597,6 +597,7 @@ async fn request_user_input_notification_overrides_pending_agent_turn_complete_n description: "Update only Plan mode.".to_string(), }]), }], + auto_resolution_ms: None, }); assert_matches!( @@ -626,6 +627,7 @@ async fn handle_request_user_input_sets_pending_notification() { description: "Update only Plan mode.".to_string(), }]), }], + auto_resolution_ms: None, }); assert_matches!(