Files
codex/codex-rs/protocol/src/request_user_input.rs
Shijie Rao e2c0837923 Make user input blocking behavior explicit (#36410)
## Why

Clients need to know whether a `request_user_input` request must wait for an
explicit response or may auto-resolve. Using `autoResolutionMs` as that signal
conflated the blocking decision with timeout policy.

## What changed

- Add required `isBlocking` fields to user input protocol and app-server
  request payloads. Plan-mode requests are blocking, while requests from other
  enabled modes are non-blocking.
- Drive the TUI's auto-resolution behavior from `isBlocking` and remove
  `autoResolutionMs` from the model-facing tool schema.
- Deprecate `autoResolutionMs` while retaining it for compatibility, and treat
  legacy payloads without `isBlocking` as blocking.

## Testing

- Cover mode-derived blocking behavior, legacy deserialization, app-server
  forwarding, delegated requests, and TUI auto-resolution.

GitOrigin-RevId: 29aade657ef743065ec264376ba567a9b353d7d7
2026-08-01 00:38:10 +00:00

104 lines
3.4 KiB
Rust

use std::collections::HashMap;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use ts_rs::TS;
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, JsonSchema, TS)]
pub struct RequestUserInputQuestionOption {
pub label: String,
pub description: String,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, JsonSchema, TS)]
pub struct RequestUserInputQuestion {
pub id: String,
pub header: String,
pub question: String,
#[serde(rename = "isOther", default)]
#[schemars(rename = "isOther")]
#[ts(rename = "isOther")]
pub is_other: bool,
#[serde(rename = "isSecret", default)]
#[schemars(rename = "isSecret")]
#[ts(rename = "isSecret")]
pub is_secret: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub options: Option<Vec<RequestUserInputQuestionOption>>,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, JsonSchema, TS)]
pub struct RequestUserInputArgs {
pub questions: Vec<RequestUserInputQuestion>,
#[serde(rename = "isBlocking")]
#[schemars(rename = "isBlocking")]
#[ts(rename = "isBlocking")]
pub is_blocking: bool,
/// @deprecated Use `isBlocking` to decide whether the request should block.
#[serde(rename = "autoResolutionMs", skip_serializing_if = "Option::is_none")]
#[schemars(rename = "autoResolutionMs")]
pub auto_resolution_ms: Option<u64>,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, JsonSchema, TS)]
pub struct RequestUserInputAnswer {
pub answers: Vec<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, JsonSchema, TS)]
pub struct RequestUserInputResponse {
pub answers: HashMap<String, RequestUserInputAnswer>,
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq, JsonSchema, TS)]
pub struct RequestUserInputEvent {
/// Responses API call id for the associated tool call, if available.
pub call_id: String,
/// Turn ID that this request belongs to.
/// Uses `#[serde(default)]` for backwards compatibility.
#[serde(default)]
pub turn_id: String,
pub questions: Vec<RequestUserInputQuestion>,
#[serde(rename = "isBlocking")]
#[schemars(rename = "isBlocking")]
#[ts(rename = "isBlocking")]
pub is_blocking: bool,
/// @deprecated Use `isBlocking` to decide whether the request should block.
#[serde(rename = "autoResolutionMs", skip_serializing_if = "Option::is_none")]
#[schemars(rename = "autoResolutionMs")]
pub auto_resolution_ms: Option<u64>,
}
impl<'de> Deserialize<'de> for RequestUserInputEvent {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
struct WireRequestUserInputEvent {
call_id: String,
#[serde(default)]
turn_id: String,
questions: Vec<RequestUserInputQuestion>,
#[serde(rename = "isBlocking")]
is_blocking: Option<bool>,
#[serde(rename = "autoResolutionMs")]
auto_resolution_ms: Option<u64>,
}
let wire = WireRequestUserInputEvent::deserialize(deserializer)?;
Ok(Self {
call_id: wire.call_id,
turn_id: wire.turn_id,
questions: wire.questions,
is_blocking: wire.is_blocking.unwrap_or(true),
auto_resolution_ms: wire.auto_resolution_ms,
})
}
}
#[cfg(test)]
#[path = "request_user_input_tests.rs"]
mod tests;