Add structured asynchronous user input requests (#42178)

## What changed

- Replace `send_user_message_async` with `request_user_input_async`, accepting one or more questions with optional suggested answers while allowing the turn to continue.
- Attach structured question metadata to asynchronous agent messages while retaining readable fallback text, and preserve it through app-server events, thread history, and generated schemas.
- Continue enabling the new tool for model catalogs that advertise either the old or new tool name.

## Testing

- Cover tool registration, question validation, emitted items, continued turn execution, history preservation, and backwards-compatible deserialization.

GitOrigin-RevId: ffc49aebde2e854c3c50a122aa52805fec3fc6b3
This commit is contained in:
rka-oai
2026-09-02 02:09:35 +00:00
committed by copyberry
parent b21f2e3081
commit 2c79ee6dac
80 changed files with 1110 additions and 146 deletions
@@ -251,6 +251,7 @@ fn final_message_from_turn_items_uses_latest_agent_message() {
phase: None,
memory_citation: None,
delivery: None,
questions: None,
},
ThreadItem::Plan {
id: "plan-1".to_string(),
@@ -262,6 +263,7 @@ fn final_message_from_turn_items_uses_latest_agent_message() {
phase: None,
memory_citation: None,
delivery: None,
questions: None,
},
]);
@@ -321,6 +323,7 @@ fn turn_completed_recovers_final_message_from_turn_items() {
phase: None,
memory_citation: None,
delivery: None,
questions: None,
}],
status: TurnStatus::Completed,
error: None,
@@ -370,6 +373,7 @@ fn turn_completed_overwrites_stale_final_message_from_turn_items() {
phase: None,
memory_citation: None,
delivery: None,
questions: None,
}],
status: TurnStatus::Completed,
error: None,
@@ -19,6 +19,7 @@ fn failed_turn_does_not_overwrite_output_last_message_file() {
phase: None,
memory_citation: None,
delivery: None,
questions: None,
},
thread_id: "thread-1".to_string(),
turn_id: "turn-1".to_string(),
+2
View File
@@ -389,6 +389,7 @@ fn turn_items_for_thread_returns_matching_turn_items() {
phase: None,
memory_citation: None,
delivery: None,
questions: None,
}],
status: codex_app_server_protocol::TurnStatus::Completed,
error: None,
@@ -420,6 +421,7 @@ fn turn_items_for_thread_returns_matching_turn_items() {
phase: None,
memory_citation: None,
delivery: None,
questions: None,
}])
);
assert_eq!(turn_items_for_thread(&thread, "missing-turn"), None);
@@ -306,6 +306,7 @@ fn unsupported_items_do_not_consume_synthetic_ids() {
phase: None,
memory_citation: None,
delivery: None,
questions: None,
},
thread_id: "thread-1".to_string(),
turn_id: "turn-1".to_string(),
@@ -928,6 +929,7 @@ fn agent_message_item_updates_final_message() {
phase: None,
memory_citation: None,
delivery: None,
questions: None,
},
thread_id: "thread-1".to_string(),
turn_id: "turn-1".to_string(),
@@ -964,6 +966,7 @@ fn agent_message_item_started_is_ignored() {
phase: None,
memory_citation: None,
delivery: None,
questions: None,
},
thread_id: "thread-1".to_string(),
turn_id: "turn-1".to_string(),
@@ -1315,6 +1318,7 @@ fn turn_completion_recovers_final_message_from_turn_items() {
phase: None,
memory_citation: None,
delivery: None,
questions: None,
}],
status: TurnStatus::Completed,
error: None,
@@ -1443,6 +1447,7 @@ fn turn_completion_overwrites_stale_final_message_from_turn_items() {
phase: None,
memory_citation: None,
delivery: None,
questions: None,
},
thread_id: "thread-1".to_string(),
turn_id: "turn-1".to_string(),
@@ -1462,6 +1467,7 @@ fn turn_completion_overwrites_stale_final_message_from_turn_items() {
phase: None,
memory_citation: None,
delivery: None,
questions: None,
}],
status: TurnStatus::Completed,
error: None,
@@ -1495,6 +1501,7 @@ fn turn_completion_preserves_streamed_final_message_when_turn_items_are_empty()
phase: None,
memory_citation: None,
delivery: None,
questions: None,
},
thread_id: "thread-1".to_string(),
turn_id: "turn-1".to_string(),
@@ -1542,6 +1549,7 @@ fn failed_turn_clears_stale_final_message() {
phase: None,
memory_citation: None,
delivery: None,
questions: None,
},
thread_id: "thread-1".to_string(),
turn_id: "turn-1".to_string(),