Feat: add isOther to question returned by request user input tool (#9890)

### Summary
Add `isOther` to question object from request_user_input tool input and
remove `other` option from the tool prompt to better handle tool input.
This commit is contained in:
Shijie Rao 2026-01-26 09:52:38 -08:00 committed by GitHub
parent 6316e57497
commit 3ba702c5b6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 25 additions and 2 deletions

View file

@ -2420,6 +2420,8 @@ pub struct ToolRequestUserInputQuestion {
pub id: String,
pub header: String,
pub question: String,
#[serde(default)]
pub is_other: bool,
pub options: Option<Vec<ToolRequestUserInputOption>>,
}

View file

@ -278,6 +278,7 @@ pub(crate) async fn apply_bespoke_event_handling(
id: question.id,
header: question.header,
question: question.question,
is_other: question.is_other,
options: question.options.map(|options| {
options
.into_iter()

View file

@ -67,6 +67,7 @@ pub fn create_request_user_input_sse_response(call_id: &str) -> anyhow::Result<S
"id": "confirm_path",
"header": "Confirm",
"question": "Proceed with the plan?",
"isOther": false,
"options": [{
"label": "Yes (Recommended)",
"description": "Continue the current plan."

View file

@ -555,7 +555,7 @@ fn create_request_user_input_tool() -> ToolSpec {
let options_schema = JsonSchema::Array {
description: Some(
"Optional 2-3 mutually exclusive choices. Put the recommended option first and suffix its label with \"(Recommended)\". Only include \"Other\" option if we want to include a free form option. If the question is free form in nature, please do not have any option."
"Optional 2-3 mutually exclusive choices. Put the recommended option first and suffix its label with \"(Recommended)\". Do not include an \"Other\" option in this list; use isOther on the question to request a free form choice. If the question is free form in nature, please do not have any option."
.to_string(),
),
items: Box::new(JsonSchema::Object {
@ -586,6 +586,15 @@ fn create_request_user_input_tool() -> ToolSpec {
description: Some("Single-sentence prompt shown to the user.".to_string()),
},
);
question_props.insert(
"isOther".to_string(),
JsonSchema::Boolean {
description: Some(
"True when this question should include a free-form \"Other\" option. Otherwise false."
.to_string(),
),
},
);
question_props.insert("options".to_string(), options_schema);
let questions_schema = JsonSchema::Array {
@ -596,6 +605,7 @@ fn create_request_user_input_tool() -> ToolSpec {
"id".to_string(),
"header".to_string(),
"question".to_string(),
"isOther".to_string(),
]),
additional_properties: Some(false.into()),
}),

View file

@ -94,6 +94,7 @@ async fn request_user_input_round_trip_resolves_pending() -> anyhow::Result<()>
"id": "confirm_path",
"header": "Confirm",
"question": "Proceed with the plan?",
"isOther": false,
"options": [{
"label": "Yes (Recommended)",
"description": "Continue the current plan."
@ -213,6 +214,7 @@ where
"id": "confirm_path",
"header": "Confirm",
"question": "Proceed with the plan?",
"isOther": false,
"options": [{
"label": "Yes (Recommended)",
"description": "Continue the current plan."

View file

@ -75,7 +75,7 @@ For complete documentation of the `Op` and `EventMsg` variants, refer to [protoc
- `EventMsg`
- `EventMsg::AgentMessage` Messages from the `Model`
- `EventMsg::ExecApprovalRequest` Request approval from user to execute a command
- `EventMsg::RequestUserInput` Request user input for a tool call
- `EventMsg::RequestUserInput` Request user input for a tool call (questions can include options plus `isOther` to add a free-form choice)
- `EventMsg::TurnComplete` A turn completed successfully
- `EventMsg::Error` A turn stopped with an error
- `EventMsg::Warning` A non-fatal warning that the client should surface to the user

View file

@ -16,6 +16,10 @@ 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(skip_serializing_if = "Option::is_none")]
pub options: Option<Vec<RequestUserInputQuestionOption>>,
}

View file

@ -508,6 +508,7 @@ mod tests {
id: id.to_string(),
header: header.to_string(),
question: "Choose an option.".to_string(),
is_other: false,
options: Some(vec![
RequestUserInputQuestionOption {
label: "Option 1".to_string(),
@ -530,6 +531,7 @@ mod tests {
id: id.to_string(),
header: header.to_string(),
question: "Share details.".to_string(),
is_other: false,
options: None,
}
}
@ -696,6 +698,7 @@ mod tests {
id: "q1".to_string(),
header: "Next Step".to_string(),
question: "What would you like to do next?".to_string(),
is_other: false,
options: Some(vec![
RequestUserInputQuestionOption {
label: "Discuss a code change (Recommended)".to_string(),