mirror of
https://github.com/openai/codex.git
synced 2026-11-06 20:20:34 +00:00
Preserve request_user_input option picker metadata
This commit is contained in:
@@ -1736,6 +1736,8 @@ mod tests {
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
item_id: "call-1".to_string(),
|
||||
input_type: None,
|
||||
option_picker_allow_multiple: None,
|
||||
questions: vec![ToolRequestUserInputQuestion {
|
||||
id: "question-1".to_string(),
|
||||
header: "Mode".to_string(),
|
||||
@@ -1797,6 +1799,8 @@ mod tests {
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
item_id: "call-1".to_string(),
|
||||
input_type: None,
|
||||
option_picker_allow_multiple: None,
|
||||
questions: vec![ToolRequestUserInputQuestion {
|
||||
id: "question-1".to_string(),
|
||||
header: "Mode".to_string(),
|
||||
|
||||
@@ -78,6 +78,7 @@ use codex_protocol::protocol::HookSource as CoreHookSource;
|
||||
use codex_protocol::protocol::ModelRerouteReason as CoreModelRerouteReason;
|
||||
use codex_protocol::protocol::ModelVerification as CoreModelVerification;
|
||||
use codex_protocol::protocol::NetworkAccess as CoreNetworkAccess;
|
||||
use codex_protocol::request_user_input::RequestUserInputType;
|
||||
use codex_protocol::protocol::NonSteerableTurnKind as CoreNonSteerableTurnKind;
|
||||
use codex_protocol::protocol::PatchApplyStatus as CorePatchApplyStatus;
|
||||
use codex_protocol::protocol::RateLimitReachedType as CoreRateLimitReachedType;
|
||||
@@ -7556,6 +7557,10 @@ pub struct ToolRequestUserInputParams {
|
||||
pub thread_id: String,
|
||||
pub turn_id: String,
|
||||
pub item_id: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub input_type: Option<RequestUserInputType>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub option_picker_allow_multiple: Option<bool>,
|
||||
pub questions: Vec<ToolRequestUserInputQuestion>,
|
||||
}
|
||||
|
||||
|
||||
@@ -840,6 +840,8 @@ pub(crate) async fn apply_bespoke_event_handling(
|
||||
thread_id: conversation_id.to_string(),
|
||||
turn_id: request.turn_id,
|
||||
item_id: request.call_id,
|
||||
input_type: request.input_type,
|
||||
option_picker_allow_multiple: request.option_picker_allow_multiple,
|
||||
questions,
|
||||
};
|
||||
let (pending_request_id, rx) = outgoing
|
||||
|
||||
@@ -10606,6 +10606,8 @@ mod tests {
|
||||
thread_id: thread_id.to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
item_id: "call-1".to_string(),
|
||||
input_type: None,
|
||||
option_picker_allow_multiple: None,
|
||||
questions: vec![],
|
||||
},
|
||||
))
|
||||
|
||||
@@ -1139,6 +1139,8 @@ mod tests {
|
||||
thread_id: thread_id.to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
item_id: "call-1".to_string(),
|
||||
input_type: None,
|
||||
option_picker_allow_multiple: None,
|
||||
questions: vec![],
|
||||
},
|
||||
))
|
||||
@@ -1197,6 +1199,8 @@ mod tests {
|
||||
thread_id: thread_id.to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
item_id: "call-1".to_string(),
|
||||
input_type: None,
|
||||
option_picker_allow_multiple: None,
|
||||
questions: vec![],
|
||||
},
|
||||
))
|
||||
|
||||
@@ -644,6 +644,8 @@ async fn handle_request_user_input(
|
||||
}
|
||||
|
||||
let args = RequestUserInputArgs {
|
||||
input_type: event.input_type,
|
||||
option_picker_allow_multiple: event.option_picker_allow_multiple,
|
||||
questions: event.questions,
|
||||
};
|
||||
let response_fut =
|
||||
|
||||
@@ -416,6 +416,8 @@ async fn delegated_mcp_guardian_abort_returns_synthetic_decline_answer() {
|
||||
&RequestUserInputEvent {
|
||||
call_id: "call-1".to_string(),
|
||||
turn_id: "child-turn-1".to_string(),
|
||||
input_type: None,
|
||||
option_picker_allow_multiple: None,
|
||||
questions: vec![RequestUserInputQuestion {
|
||||
id: format!("{MCP_TOOL_APPROVAL_QUESTION_ID_PREFIX}_call-1"),
|
||||
header: "Approve app tool call?".to_string(),
|
||||
|
||||
@@ -250,6 +250,8 @@ async fn should_install_mcp_dependencies(
|
||||
]),
|
||||
};
|
||||
let args = RequestUserInputArgs {
|
||||
input_type: None,
|
||||
option_picker_allow_multiple: None,
|
||||
questions: vec![question],
|
||||
};
|
||||
let sub_id = &turn_context.sub_id;
|
||||
|
||||
@@ -1051,6 +1051,8 @@ async fn maybe_request_mcp_tool_approval(
|
||||
}
|
||||
|
||||
let args = RequestUserInputArgs {
|
||||
input_type: None,
|
||||
option_picker_allow_multiple: None,
|
||||
questions: vec![question],
|
||||
};
|
||||
let response = sess
|
||||
|
||||
@@ -2135,10 +2135,17 @@ impl Session {
|
||||
warn!("Overwriting existing pending user input for sub_id: {event_id}");
|
||||
}
|
||||
|
||||
let RequestUserInputArgs {
|
||||
input_type,
|
||||
option_picker_allow_multiple,
|
||||
questions,
|
||||
} = args;
|
||||
let event = EventMsg::RequestUserInput(RequestUserInputEvent {
|
||||
call_id,
|
||||
turn_id: turn_context.sub_id.clone(),
|
||||
questions: args.questions,
|
||||
input_type,
|
||||
option_picker_allow_multiple,
|
||||
questions,
|
||||
});
|
||||
self.send_event(turn_context, event).await;
|
||||
rx_response.await.ok()
|
||||
|
||||
@@ -139,7 +139,11 @@ async fn request_skill_dependencies(
|
||||
.request_user_input(
|
||||
turn_context,
|
||||
format!("skill-deps-{}", turn_context.sub_id),
|
||||
RequestUserInputArgs { questions },
|
||||
RequestUserInputArgs {
|
||||
input_type: None,
|
||||
option_picker_allow_multiple: None,
|
||||
questions,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap_or_else(|| RequestUserInputResponse {
|
||||
|
||||
@@ -5,6 +5,13 @@ use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use ts_rs::TS;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum RequestUserInputType {
|
||||
Structured,
|
||||
OptionPicker,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, JsonSchema, TS)]
|
||||
pub struct RequestUserInputQuestionOption {
|
||||
pub label: String,
|
||||
@@ -30,6 +37,22 @@ pub struct RequestUserInputQuestion {
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, JsonSchema, TS)]
|
||||
pub struct RequestUserInputArgs {
|
||||
#[serde(
|
||||
rename = "inputType",
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
#[schemars(rename = "inputType")]
|
||||
#[ts(rename = "inputType")]
|
||||
pub input_type: Option<RequestUserInputType>,
|
||||
#[serde(
|
||||
rename = "optionPickerAllowMultiple",
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
#[schemars(rename = "optionPickerAllowMultiple")]
|
||||
#[ts(rename = "optionPickerAllowMultiple")]
|
||||
pub option_picker_allow_multiple: Option<bool>,
|
||||
pub questions: Vec<RequestUserInputQuestion>,
|
||||
}
|
||||
|
||||
@@ -51,5 +74,21 @@ pub struct RequestUserInputEvent {
|
||||
/// Uses `#[serde(default)]` for backwards compatibility.
|
||||
#[serde(default)]
|
||||
pub turn_id: String,
|
||||
#[serde(
|
||||
rename = "inputType",
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
#[schemars(rename = "inputType")]
|
||||
#[ts(rename = "inputType")]
|
||||
pub input_type: Option<RequestUserInputType>,
|
||||
#[serde(
|
||||
rename = "optionPickerAllowMultiple",
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
#[schemars(rename = "optionPickerAllowMultiple")]
|
||||
#[ts(rename = "optionPickerAllowMultiple")]
|
||||
pub option_picker_allow_multiple: Option<bool>,
|
||||
pub questions: Vec<RequestUserInputQuestion>,
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::ToolSpec;
|
||||
use codex_protocol::config_types::ModeKind;
|
||||
use codex_protocol::config_types::TUI_VISIBLE_COLLABORATION_MODES;
|
||||
use codex_protocol::request_user_input::RequestUserInputArgs;
|
||||
use serde_json::json;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
pub const REQUEST_USER_INPUT_TOOL_NAME: &str = "request_user_input";
|
||||
@@ -67,7 +68,26 @@ 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 properties = BTreeMap::from([
|
||||
(
|
||||
"inputType".to_string(),
|
||||
JsonSchema::string_enum(
|
||||
vec![json!("structured"), json!("option_picker")],
|
||||
Some(
|
||||
"Optional UI presentation hint. Use `option_picker` when the prompt should render as a selectable option picker; otherwise omit this or use `structured`."
|
||||
.to_string(),
|
||||
),
|
||||
),
|
||||
),
|
||||
(
|
||||
"optionPickerAllowMultiple".to_string(),
|
||||
JsonSchema::boolean(Some(
|
||||
"Optional. For `inputType: option_picker`, set true when multiple choices may be selected."
|
||||
.to_string(),
|
||||
)),
|
||||
),
|
||||
("questions".to_string(), questions_schema),
|
||||
]);
|
||||
|
||||
ToolSpec::Function(ResponsesApiTool {
|
||||
name: REQUEST_USER_INPUT_TOOL_NAME.to_string(),
|
||||
|
||||
@@ -13,7 +13,25 @@ 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([
|
||||
(
|
||||
"inputType".to_string(),
|
||||
JsonSchema::string_enum(
|
||||
vec![json!("structured"), json!("option_picker")],
|
||||
Some(
|
||||
"Optional UI presentation hint. Use `option_picker` when the prompt should render as a selectable option picker; otherwise omit this or use `structured`."
|
||||
.to_string(),
|
||||
),
|
||||
),
|
||||
),
|
||||
(
|
||||
"optionPickerAllowMultiple".to_string(),
|
||||
JsonSchema::boolean(Some(
|
||||
"Optional. For `inputType: option_picker`, set true when multiple choices may be selected."
|
||||
.to_string(),
|
||||
)),
|
||||
),
|
||||
(
|
||||
"questions".to_string(),
|
||||
JsonSchema::array(
|
||||
JsonSchema::object(
|
||||
@@ -83,7 +101,8 @@ 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,
|
||||
})
|
||||
);
|
||||
|
||||
@@ -524,6 +524,8 @@ mod tests {
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-2".to_string(),
|
||||
item_id: "tool-1".to_string(),
|
||||
input_type: None,
|
||||
option_picker_allow_multiple: None,
|
||||
questions: Vec::new(),
|
||||
},
|
||||
}),
|
||||
@@ -817,6 +819,8 @@ mod tests {
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
item_id: "tool-1".to_string(),
|
||||
input_type: None,
|
||||
option_picker_allow_multiple: None,
|
||||
questions: Vec::new(),
|
||||
},
|
||||
});
|
||||
@@ -839,6 +843,8 @@ mod tests {
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
item_id: item_id.to_string(),
|
||||
input_type: None,
|
||||
option_picker_allow_multiple: None,
|
||||
questions: Vec::new(),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -609,6 +609,8 @@ mod tests {
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: turn_id.to_string(),
|
||||
item_id: call_id.to_string(),
|
||||
input_type: None,
|
||||
option_picker_allow_multiple: None,
|
||||
questions: Vec::new(),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -4078,6 +4078,8 @@ fn request_user_input_request(thread_id: ThreadId, turn_id: &str, item_id: &str)
|
||||
thread_id: thread_id.to_string(),
|
||||
turn_id: turn_id.to_string(),
|
||||
item_id: item_id.to_string(),
|
||||
input_type: None,
|
||||
option_picker_allow_multiple: None,
|
||||
questions: Vec::new(),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1487,6 +1487,8 @@ mod tests {
|
||||
RequestUserInputEvent {
|
||||
call_id: "call-1".to_string(),
|
||||
turn_id: turn_id.to_string(),
|
||||
input_type: None,
|
||||
option_picker_allow_multiple: None,
|
||||
questions,
|
||||
}
|
||||
}
|
||||
@@ -1548,11 +1550,15 @@ mod tests {
|
||||
overlay.try_consume_user_input_request(RequestUserInputEvent {
|
||||
call_id: "call-2".to_string(),
|
||||
turn_id: "turn-2".to_string(),
|
||||
input_type: None,
|
||||
option_picker_allow_multiple: None,
|
||||
questions: vec![question_with_options("q2", "Second")],
|
||||
});
|
||||
overlay.try_consume_user_input_request(RequestUserInputEvent {
|
||||
call_id: "call-3".to_string(),
|
||||
turn_id: "turn-3".to_string(),
|
||||
input_type: None,
|
||||
option_picker_allow_multiple: None,
|
||||
questions: vec![question_with_options("q3", "Third")],
|
||||
});
|
||||
|
||||
@@ -1569,6 +1575,8 @@ mod tests {
|
||||
RequestUserInputEvent {
|
||||
call_id: "call-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
input_type: None,
|
||||
option_picker_allow_multiple: None,
|
||||
questions: vec![question_with_options("q1", "First")],
|
||||
},
|
||||
tx,
|
||||
@@ -1596,6 +1604,8 @@ mod tests {
|
||||
RequestUserInputEvent {
|
||||
call_id: "call-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
input_type: None,
|
||||
option_picker_allow_multiple: None,
|
||||
questions: vec![question_with_options("q1", "First")],
|
||||
},
|
||||
tx,
|
||||
@@ -1606,6 +1616,8 @@ mod tests {
|
||||
overlay.try_consume_user_input_request(RequestUserInputEvent {
|
||||
call_id: "call-2".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
input_type: None,
|
||||
option_picker_allow_multiple: None,
|
||||
questions: vec![question_with_options("q2", "Second")],
|
||||
});
|
||||
|
||||
@@ -1632,6 +1644,8 @@ mod tests {
|
||||
RequestUserInputEvent {
|
||||
call_id: "call-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
input_type: None,
|
||||
option_picker_allow_multiple: None,
|
||||
questions: vec![question_with_options("q1", "First")],
|
||||
},
|
||||
tx,
|
||||
@@ -1642,11 +1656,15 @@ mod tests {
|
||||
overlay.try_consume_user_input_request(RequestUserInputEvent {
|
||||
call_id: "call-2".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
input_type: None,
|
||||
option_picker_allow_multiple: None,
|
||||
questions: vec![question_with_options("q2", "Second")],
|
||||
});
|
||||
overlay.try_consume_user_input_request(RequestUserInputEvent {
|
||||
call_id: "call-3".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
input_type: None,
|
||||
option_picker_allow_multiple: None,
|
||||
questions: vec![question_with_options("q3", "Third")],
|
||||
});
|
||||
|
||||
|
||||
@@ -1908,6 +1908,8 @@ fn request_user_input_from_params(params: ToolRequestUserInputParams) -> Request
|
||||
RequestUserInputEvent {
|
||||
turn_id: params.turn_id,
|
||||
call_id: params.item_id,
|
||||
input_type: params.input_type,
|
||||
option_picker_allow_multiple: params.option_picker_allow_multiple,
|
||||
questions: params
|
||||
.questions
|
||||
.into_iter()
|
||||
|
||||
@@ -161,6 +161,8 @@ mod tests {
|
||||
RequestUserInputEvent {
|
||||
call_id: call_id.to_string(),
|
||||
turn_id: turn_id.to_string(),
|
||||
input_type: None,
|
||||
option_picker_allow_multiple: None,
|
||||
questions: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -585,6 +585,8 @@ async fn request_user_input_notification_overrides_pending_agent_turn_complete_n
|
||||
chat.handle_request_user_input_now(RequestUserInputEvent {
|
||||
call_id: "call-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
input_type: None,
|
||||
option_picker_allow_multiple: None,
|
||||
questions: vec![RequestUserInputQuestion {
|
||||
id: "reasoning_scope".to_string(),
|
||||
header: "Reasoning scope".to_string(),
|
||||
@@ -613,6 +615,8 @@ async fn handle_request_user_input_sets_pending_notification() {
|
||||
chat.handle_request_user_input_now(RequestUserInputEvent {
|
||||
call_id: "call-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
input_type: None,
|
||||
option_picker_allow_multiple: None,
|
||||
questions: vec![RequestUserInputQuestion {
|
||||
id: "reasoning_scope".to_string(),
|
||||
header: "Reasoning scope".to_string(),
|
||||
|
||||
Reference in New Issue
Block a user