[codex] Add user input client ids (#24653)

## Summary

Adds an optional `clientId` field to app-server v2 `UserInput` and
carries it through the core `UserInput` model so clients can correlate
echoed user input items without relying on payload equality.

## Details

- Adds `client_id: Option<String>` to core `UserInput` variants.
- Exposes the v2 app-server field as `clientId` on the wire and in
generated TypeScript.
- Preserves the id when converting between app-server v2 and core
protocol types.
- Regenerates app-server schema fixtures.

## Validation

- `just fmt`
- `just write-app-server-schema`
- `cargo test -p codex-app-server-protocol`
- `cargo test -p codex-protocol`
- `just fix -p codex-app-server-protocol`
- `just fix -p codex-protocol`
- `git diff --check`
This commit is contained in:
Alexi Christakis authored and GitHub committed 2026-05-28 14:54:39 -07:00
1 parent a027135bc6
commit e92c952b2e
113 files changed
+793 -97

No files matched your search

@@ -213,6 +213,7 @@ mod thread_processor_behavior_tests {
fn thread_turns_list_merges_in_progress_active_turn_before_agent_status_running() {
let persisted_items = vec![RolloutItem::EventMsg(EventMsg::UserMessage(
codex_protocol::protocol::UserMessageEvent {
client_id: None,
message: "persisted".to_string(),
images: None,
local_images: Vec::new(),
@@ -224,6 +225,7 @@ mod thread_processor_behavior_tests {
id: "live-turn".to_string(),
items: vec![ThreadItem::UserMessage {
id: "live-user-message".to_string(),
client_id: None,
content: vec![V2UserInput::Text {
text: "live".to_string(),
text_elements: Vec::new(),
@@ -148,6 +148,7 @@ mod tests {
fn token_usage_history() -> Vec<RolloutItem> {
vec![
RolloutItem::EventMsg(EventMsg::UserMessage(UserMessageEvent {
client_id: None,
message: "first turn".to_string(),
images: None,
local_images: Vec::new(),
@@ -164,6 +165,7 @@ mod tests {
rate_limits: None,
})),
RolloutItem::EventMsg(EventMsg::UserMessage(UserMessageEvent {
client_id: None,
message: "second turn".to_string(),
images: None,
local_images: Vec::new(),
@@ -416,6 +416,7 @@ impl TurnRequestProcessor {
.into_iter()
.map(V2UserInput::into_core)
.collect();
let client_user_message_id = params.client_user_message_id;
let additional_context = map_additional_context(params.additional_context);
let turn_has_input = !mapped_items.is_empty();
let thread_settings = self
@@ -448,8 +449,12 @@ impl TurnRequestProcessor {
additional_context,
thread_settings,
};
let turn_id = self
.submit_core_op(&request_id, thread.as_ref(), turn_op)
let turn_id = thread
.submit_user_input_with_client_user_message_id(
turn_op,
self.request_trace_context(&request_id).await,
client_user_message_id,
)
.await
.map_err(|err| {
let error = internal_error(format!("failed to start turn: {err}"));
@@ -780,6 +785,7 @@ impl TurnRequestProcessor {
mapped_items,
additional_context,
Some(&params.expected_turn_id),
params.client_user_message_id,
params.responsesapi_client_metadata,
)
.await
@@ -989,6 +995,7 @@ impl TurnRequestProcessor {
} else {
vec![ThreadItem::UserMessage {
id: turn_id.clone(),
client_id: None,
content: vec![V2UserInput::Text {
text: display_text.to_string(),
// Review prompt display text is synthesized; no UI element ranges to preserve.