mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Add request_user_input auto-resolution window contract (#27256)
## Why `request_user_input` is moving beyond its original plan-mode-only workflow, and future default/goal-mode usage needs a way for the model to ask helpful but non-blocking questions without forcing the turn to wait forever. This PR adds an explicit `autoResolutionMs` contract so a later client/runtime change can auto-resolve unanswered prompts after a bounded window while leaving truly blocking questions unchanged. This is contract plumbing only; it does not implement the client-side timer or auto-selection behavior, and the model-facing description treats the field as reserved unless the current runtime explicitly supports auto-resolution. ## What Changed - Added optional `autoResolutionMs` to the model-facing `request_user_input` args and core `RequestUserInputEvent`. - Added model-facing schema text for `autoResolutionMs` while marking it reserved for runtimes that explicitly support auto-resolution. - Bounds `autoResolutionMs` to `60_000..=240_000` ms during argument normalization by clamping out-of-range model-provided values. - Propagated the field through app-server v2 `ToolRequestUserInputParams`, app-server request forwarding, generated TypeScript, and JSON schema fixtures. - Updated app-server, core, protocol, and TUI call sites/tests so omitted values preserve existing `None`/`null` behavior and coverage verifies a `Some(60_000)` round trip. ## Verification - `just test -p codex-app-server-protocol` - `just test -p codex-core request_user_input` - `just test -p codex-app-server request_user_input_round_trip` - `just test -p codex-tui request_user_input` - `just test -p codex-protocol`
This commit is contained in:
@@ -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<u64>,
|
||||
) -> 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)]
|
||||
|
||||
Reference in New Issue
Block a user