From 7bc32d4c4cfe898e36a007b93bbb3d9bfc5cee0b Mon Sep 17 00:00:00 2001 From: Alexi Christakis <167903946+alexi-openai@users.noreply.github.com> Date: Tue, 26 May 2026 15:07:14 -0700 Subject: [PATCH 1/6] Add user input client ids --- .../schema/json/ClientRequest.json | 35 +++++++ .../schema/json/ServerNotification.json | 35 +++++++ .../codex_app_server_protocol.schemas.json | 35 +++++++ .../codex_app_server_protocol.v2.schemas.json | 35 +++++++ .../json/v2/ItemCompletedNotification.json | 35 +++++++ .../json/v2/ItemStartedNotification.json | 35 +++++++ .../schema/json/v2/ReviewStartResponse.json | 35 +++++++ .../schema/json/v2/ThreadForkResponse.json | 35 +++++++ .../schema/json/v2/ThreadListResponse.json | 35 +++++++ .../json/v2/ThreadMetadataUpdateResponse.json | 35 +++++++ .../schema/json/v2/ThreadReadResponse.json | 35 +++++++ .../schema/json/v2/ThreadResumeResponse.json | 35 +++++++ .../json/v2/ThreadRollbackResponse.json | 35 +++++++ .../schema/json/v2/ThreadStartResponse.json | 35 +++++++ .../json/v2/ThreadStartedNotification.json | 35 +++++++ .../json/v2/ThreadUnarchiveResponse.json | 35 +++++++ .../json/v2/TurnCompletedNotification.json | 35 +++++++ .../schema/json/v2/TurnStartParams.json | 35 +++++++ .../schema/json/v2/TurnStartResponse.json | 35 +++++++ .../json/v2/TurnStartedNotification.json | 35 +++++++ .../schema/json/v2/TurnSteerParams.json | 35 +++++++ .../schema/typescript/v2/UserInput.ts | 4 +- .../src/protocol/thread_history.rs | 21 +++++ .../src/protocol/v2/tests.rs | 63 +++++++++++++ .../src/protocol/v2/turn.rs | 93 +++++++++++++++++-- codex-rs/protocol/src/items.rs | 1 + codex-rs/protocol/src/models.rs | 14 ++- codex-rs/protocol/src/protocol.rs | 4 + codex-rs/protocol/src/user_input.rs | 15 ++- 29 files changed, 937 insertions(+), 13 deletions(-) diff --git a/codex-rs/app-server-protocol/schema/json/ClientRequest.json b/codex-rs/app-server-protocol/schema/json/ClientRequest.json index a09d793bfef..ed54a7935a0 100644 --- a/codex-rs/app-server-protocol/schema/json/ClientRequest.json +++ b/codex-rs/app-server-protocol/schema/json/ClientRequest.json @@ -4096,6 +4096,13 @@ "oneOf": [ { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "text": { "type": "string" }, @@ -4124,6 +4131,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "detail": { "anyOf": [ { @@ -4155,6 +4169,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "detail": { "anyOf": [ { @@ -4186,6 +4207,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "name": { "type": "string" }, @@ -4210,6 +4238,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "name": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/ServerNotification.json b/codex-rs/app-server-protocol/schema/json/ServerNotification.json index 2c290558da8..3035187c410 100644 --- a/codex-rs/app-server-protocol/schema/json/ServerNotification.json +++ b/codex-rs/app-server-protocol/schema/json/ServerNotification.json @@ -4945,6 +4945,13 @@ "oneOf": [ { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "text": { "type": "string" }, @@ -4973,6 +4980,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "detail": { "anyOf": [ { @@ -5004,6 +5018,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "detail": { "anyOf": [ { @@ -5035,6 +5056,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "name": { "type": "string" }, @@ -5059,6 +5087,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "name": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json index c5ce0f86229..d7ad26e96dd 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -18605,6 +18605,13 @@ "oneOf": [ { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "text": { "type": "string" }, @@ -18633,6 +18640,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "detail": { "anyOf": [ { @@ -18664,6 +18678,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "detail": { "anyOf": [ { @@ -18695,6 +18716,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "name": { "type": "string" }, @@ -18719,6 +18747,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "name": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json index 92134a2df3c..3e48699cb9f 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json @@ -16429,6 +16429,13 @@ "oneOf": [ { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "text": { "type": "string" }, @@ -16457,6 +16464,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "detail": { "anyOf": [ { @@ -16488,6 +16502,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "detail": { "anyOf": [ { @@ -16519,6 +16540,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "name": { "type": "string" }, @@ -16543,6 +16571,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "name": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/v2/ItemCompletedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/ItemCompletedNotification.json index cea3dae7d21..c2bd5f96c91 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ItemCompletedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ItemCompletedNotification.json @@ -1166,6 +1166,13 @@ "oneOf": [ { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "text": { "type": "string" }, @@ -1194,6 +1201,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "detail": { "anyOf": [ { @@ -1225,6 +1239,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "detail": { "anyOf": [ { @@ -1256,6 +1277,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "name": { "type": "string" }, @@ -1280,6 +1308,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "name": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/v2/ItemStartedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/ItemStartedNotification.json index 529667388ec..98d46cf5fac 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ItemStartedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ItemStartedNotification.json @@ -1166,6 +1166,13 @@ "oneOf": [ { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "text": { "type": "string" }, @@ -1194,6 +1201,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "detail": { "anyOf": [ { @@ -1225,6 +1239,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "detail": { "anyOf": [ { @@ -1256,6 +1277,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "name": { "type": "string" }, @@ -1280,6 +1308,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "name": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/v2/ReviewStartResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ReviewStartResponse.json index df93fbbea53..ebcb1633b40 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ReviewStartResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ReviewStartResponse.json @@ -1439,6 +1439,13 @@ "oneOf": [ { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "text": { "type": "string" }, @@ -1467,6 +1474,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "detail": { "anyOf": [ { @@ -1498,6 +1512,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "detail": { "anyOf": [ { @@ -1529,6 +1550,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "name": { "type": "string" }, @@ -1553,6 +1581,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "name": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json index a9a2529fc59..cd5c1f59ddd 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json @@ -1999,6 +1999,13 @@ "oneOf": [ { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "text": { "type": "string" }, @@ -2027,6 +2034,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "detail": { "anyOf": [ { @@ -2058,6 +2072,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "detail": { "anyOf": [ { @@ -2089,6 +2110,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "name": { "type": "string" }, @@ -2113,6 +2141,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "name": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json index 8ccceaaa7c2..730547bef9b 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json @@ -1814,6 +1814,13 @@ "oneOf": [ { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "text": { "type": "string" }, @@ -1842,6 +1849,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "detail": { "anyOf": [ { @@ -1873,6 +1887,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "detail": { "anyOf": [ { @@ -1904,6 +1925,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "name": { "type": "string" }, @@ -1928,6 +1956,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "name": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json index 87af03ea625..6f7a52f750e 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json @@ -1814,6 +1814,13 @@ "oneOf": [ { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "text": { "type": "string" }, @@ -1842,6 +1849,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "detail": { "anyOf": [ { @@ -1873,6 +1887,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "detail": { "anyOf": [ { @@ -1904,6 +1925,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "name": { "type": "string" }, @@ -1928,6 +1956,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "name": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json index a6d86c34858..fe79798fb18 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json @@ -1814,6 +1814,13 @@ "oneOf": [ { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "text": { "type": "string" }, @@ -1842,6 +1849,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "detail": { "anyOf": [ { @@ -1873,6 +1887,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "detail": { "anyOf": [ { @@ -1904,6 +1925,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "name": { "type": "string" }, @@ -1928,6 +1956,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "name": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json index 8f76893af8f..259eb450b9a 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json @@ -2025,6 +2025,13 @@ "oneOf": [ { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "text": { "type": "string" }, @@ -2053,6 +2060,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "detail": { "anyOf": [ { @@ -2084,6 +2098,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "detail": { "anyOf": [ { @@ -2115,6 +2136,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "name": { "type": "string" }, @@ -2139,6 +2167,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "name": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json index 4256d13ec43..9d21bebb476 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json @@ -1814,6 +1814,13 @@ "oneOf": [ { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "text": { "type": "string" }, @@ -1842,6 +1849,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "detail": { "anyOf": [ { @@ -1873,6 +1887,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "detail": { "anyOf": [ { @@ -1904,6 +1925,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "name": { "type": "string" }, @@ -1928,6 +1956,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "name": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json index 6ab655a8bc2..8a52067d125 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json @@ -1999,6 +1999,13 @@ "oneOf": [ { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "text": { "type": "string" }, @@ -2027,6 +2034,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "detail": { "anyOf": [ { @@ -2058,6 +2072,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "detail": { "anyOf": [ { @@ -2089,6 +2110,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "name": { "type": "string" }, @@ -2113,6 +2141,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "name": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json index 91bc13a9c63..814ccadeeb5 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json @@ -1814,6 +1814,13 @@ "oneOf": [ { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "text": { "type": "string" }, @@ -1842,6 +1849,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "detail": { "anyOf": [ { @@ -1873,6 +1887,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "detail": { "anyOf": [ { @@ -1904,6 +1925,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "name": { "type": "string" }, @@ -1928,6 +1956,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "name": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json index 0b68bfa957f..79fcc0c701e 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json @@ -1814,6 +1814,13 @@ "oneOf": [ { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "text": { "type": "string" }, @@ -1842,6 +1849,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "detail": { "anyOf": [ { @@ -1873,6 +1887,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "detail": { "anyOf": [ { @@ -1904,6 +1925,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "name": { "type": "string" }, @@ -1928,6 +1956,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "name": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/v2/TurnCompletedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/TurnCompletedNotification.json index 4856f73529e..90d4150f7d3 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/TurnCompletedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnCompletedNotification.json @@ -1439,6 +1439,13 @@ "oneOf": [ { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "text": { "type": "string" }, @@ -1467,6 +1474,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "detail": { "anyOf": [ { @@ -1498,6 +1512,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "detail": { "anyOf": [ { @@ -1529,6 +1550,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "name": { "type": "string" }, @@ -1553,6 +1581,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "name": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/v2/TurnStartParams.json b/codex-rs/app-server-protocol/schema/json/v2/TurnStartParams.json index 17b69a698e8..065cd2caca4 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/TurnStartParams.json +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnStartParams.json @@ -354,6 +354,13 @@ "oneOf": [ { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "text": { "type": "string" }, @@ -382,6 +389,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "detail": { "anyOf": [ { @@ -413,6 +427,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "detail": { "anyOf": [ { @@ -444,6 +465,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "name": { "type": "string" }, @@ -468,6 +496,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "name": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/v2/TurnStartResponse.json b/codex-rs/app-server-protocol/schema/json/v2/TurnStartResponse.json index c5e48ae6ce2..2877b9e69ca 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/TurnStartResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnStartResponse.json @@ -1439,6 +1439,13 @@ "oneOf": [ { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "text": { "type": "string" }, @@ -1467,6 +1474,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "detail": { "anyOf": [ { @@ -1498,6 +1512,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "detail": { "anyOf": [ { @@ -1529,6 +1550,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "name": { "type": "string" }, @@ -1553,6 +1581,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "name": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/v2/TurnStartedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/TurnStartedNotification.json index 075699a34e6..3ce4b697d5f 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/TurnStartedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnStartedNotification.json @@ -1439,6 +1439,13 @@ "oneOf": [ { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "text": { "type": "string" }, @@ -1467,6 +1474,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "detail": { "anyOf": [ { @@ -1498,6 +1512,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "detail": { "anyOf": [ { @@ -1529,6 +1550,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "name": { "type": "string" }, @@ -1553,6 +1581,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "name": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/v2/TurnSteerParams.json b/codex-rs/app-server-protocol/schema/json/v2/TurnSteerParams.json index 6f5452de668..2ea95e77ac0 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/TurnSteerParams.json +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnSteerParams.json @@ -78,6 +78,13 @@ "oneOf": [ { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "text": { "type": "string" }, @@ -106,6 +113,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "detail": { "anyOf": [ { @@ -137,6 +151,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "detail": { "anyOf": [ { @@ -168,6 +189,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "name": { "type": "string" }, @@ -192,6 +220,13 @@ }, { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "name": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/UserInput.ts b/codex-rs/app-server-protocol/schema/typescript/v2/UserInput.ts index 2ac37c5228a..b0ecac337a5 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/UserInput.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/UserInput.ts @@ -4,8 +4,8 @@ import type { ImageDetail } from "../ImageDetail"; import type { TextElement } from "./TextElement"; -export type UserInput = { "type": "text", text: string, +export type UserInput = { "type": "text", clientId?: string, text: string, /** * UI-defined spans within `text` used to render or persist special elements. */ -text_elements: Array, } | { "type": "image", detail?: ImageDetail, url: string, } | { "type": "localImage", detail?: ImageDetail, path: string, } | { "type": "skill", name: string, path: string, } | { "type": "mention", name: string, path: string, }; +text_elements: Array, } | { "type": "image", clientId?: string, detail?: ImageDetail, url: string, } | { "type": "localImage", clientId?: string, detail?: ImageDetail, path: string, } | { "type": "skill", clientId?: string, name: string, path: string, } | { "type": "mention", clientId?: string, name: string, path: string, }; diff --git a/codex-rs/app-server-protocol/src/protocol/thread_history.rs b/codex-rs/app-server-protocol/src/protocol/thread_history.rs index c0c7d555283..b424e4a8659 100644 --- a/codex-rs/app-server-protocol/src/protocol/thread_history.rs +++ b/codex-rs/app-server-protocol/src/protocol/thread_history.rs @@ -1069,6 +1069,7 @@ impl ThreadHistoryBuilder { let mut content = Vec::new(); if !payload.message.trim().is_empty() { content.push(UserInput::Text { + client_id: None, text: payload.message.clone(), text_elements: payload .text_elements @@ -1081,6 +1082,7 @@ impl ThreadHistoryBuilder { if let Some(images) = &payload.images { for (idx, image) in images.iter().enumerate() { content.push(UserInput::Image { + client_id: None, url: image.clone(), detail: payload.image_details.get(idx).copied().flatten(), }); @@ -1088,6 +1090,7 @@ impl ThreadHistoryBuilder { } for (idx, path) in payload.local_images.iter().enumerate() { content.push(UserInput::LocalImage { + client_id: None, path: path.clone(), detail: payload.local_image_details.get(idx).copied().flatten(), }); @@ -1294,10 +1297,12 @@ mod tests { id: "item-1".into(), content: vec![ UserInput::Text { + client_id: None, text: "First turn".into(), text_elements: Vec::new(), }, UserInput::Image { + client_id: None, url: "https://example.com/one.png".into(), detail: None, } @@ -1331,6 +1336,7 @@ mod tests { ThreadItem::UserMessage { id: "item-4".into(), content: vec![UserInput::Text { + client_id: None, text: "Second turn".into(), text_elements: Vec::new(), }], @@ -1370,14 +1376,17 @@ mod tests { id: "item-1".into(), content: vec![ UserInput::Text { + client_id: None, text: "inspect these".into(), text_elements: Vec::new(), }, UserInput::Image { + client_id: None, url: "https://example.com/image.png".into(), detail: Some(ImageDetail::Original), }, UserInput::LocalImage { + client_id: None, path: local_path, detail: Some(ImageDetail::Original), }, @@ -1435,6 +1444,7 @@ mod tests { ThreadItem::UserMessage { id: "item-1".into(), content: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -1516,6 +1526,7 @@ mod tests { ThreadItem::UserMessage { id: "item-1".into(), content: vec![UserInput::Text { + client_id: None, text: "generate an image".into(), text_elements: Vec::new(), }], @@ -1635,6 +1646,7 @@ mod tests { ThreadItem::UserMessage { id: "item-1".into(), content: vec![UserInput::Text { + client_id: None, text: "Please do the thing".into(), text_elements: Vec::new(), }], @@ -1658,6 +1670,7 @@ mod tests { ThreadItem::UserMessage { id: "item-3".into(), content: vec![UserInput::Text { + client_id: None, text: "Let's try again".into(), text_elements: Vec::new(), }], @@ -1733,6 +1746,7 @@ mod tests { ThreadItem::UserMessage { id: "item-1".into(), content: vec![UserInput::Text { + client_id: None, text: "First".into(), text_elements: Vec::new(), }], @@ -1751,6 +1765,7 @@ mod tests { ThreadItem::UserMessage { id: "item-3".into(), content: vec![UserInput::Text { + client_id: None, text: "Third".into(), text_elements: Vec::new(), }], @@ -1849,6 +1864,7 @@ mod tests { ThreadItem::UserMessage { id: "item-1".into(), content: vec![UserInput::Text { + client_id: None, text: "Start".into(), text_elements: Vec::new(), }], @@ -1856,6 +1872,7 @@ mod tests { ThreadItem::UserMessage { id: "item-2".into(), content: vec![UserInput::Text { + client_id: None, text: "Steer".into(), text_elements: Vec::new(), }], @@ -2529,6 +2546,7 @@ mod tests { ThreadItem::UserMessage { id: "item-2".into(), content: vec![UserInput::Text { + client_id: None, text: "second".into(), text_elements: Vec::new(), }], @@ -2585,6 +2603,7 @@ mod tests { ThreadItem::UserMessage { id: "item-1".into(), content: vec![UserInput::Text { + client_id: None, text: "apply patch".into(), text_elements: Vec::new(), }], @@ -2653,6 +2672,7 @@ mod tests { ThreadItem::UserMessage { id: "item-1".into(), content: vec![UserInput::Text { + client_id: None, text: "apply patch".into(), text_elements: Vec::new(), }], @@ -3106,6 +3126,7 @@ mod tests { items: vec![ThreadItem::UserMessage { id: "item-1".into(), content: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server-protocol/src/protocol/v2/tests.rs b/codex-rs/app-server-protocol/src/protocol/v2/tests.rs index 90273cd8681..e49c55be126 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2/tests.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2/tests.rs @@ -2316,22 +2316,27 @@ fn core_turn_item_into_thread_item_converts_supported_variants() { id: "user-1".to_string(), content: vec![ CoreUserInput::Text { + client_id: Some("client-text-1".to_string()), text: "hello".to_string(), text_elements: Vec::new(), }, CoreUserInput::Image { + client_id: Some("client-image-1".to_string()), image_url: "https://example.com/image.png".to_string(), detail: Some(ImageDetail::Original), }, CoreUserInput::LocalImage { + client_id: Some("client-local-image-1".to_string()), path: PathBuf::from("local/image.png"), detail: Some(ImageDetail::Original), }, CoreUserInput::Skill { + client_id: Some("client-skill-1".to_string()), name: "skill-creator".to_string(), path: PathBuf::from("/repo/.codex/skills/skill-creator/SKILL.md"), }, CoreUserInput::Mention { + client_id: Some("client-mention-1".to_string()), name: "Demo App".to_string(), path: "app://demo-app".to_string(), }, @@ -2344,22 +2349,27 @@ fn core_turn_item_into_thread_item_converts_supported_variants() { id: "user-1".to_string(), content: vec![ UserInput::Text { + client_id: Some("client-text-1".to_string()), text: "hello".to_string(), text_elements: Vec::new(), }, UserInput::Image { + client_id: Some("client-image-1".to_string()), url: "https://example.com/image.png".to_string(), detail: Some(ImageDetail::Original), }, UserInput::LocalImage { + client_id: Some("client-local-image-1".to_string()), path: PathBuf::from("local/image.png"), detail: Some(ImageDetail::Original), }, UserInput::Skill { + client_id: Some("client-skill-1".to_string()), name: "skill-creator".to_string(), path: PathBuf::from("/repo/.codex/skills/skill-creator/SKILL.md"), }, UserInput::Mention { + client_id: Some("client-mention-1".to_string()), name: "Demo App".to_string(), path: "app://demo-app".to_string(), }, @@ -2572,15 +2582,66 @@ fn core_turn_item_into_thread_item_converts_supported_variants() { ); } +#[test] +fn user_input_client_id_uses_camel_case_wire_field() { + let input = UserInput::Text { + client_id: Some("client-text-1".to_string()), + text: "hello".to_string(), + text_elements: Vec::new(), + }; + + let value = serde_json::to_value(&input).expect("serialize user input"); + assert_eq!( + value, + json!({ + "type": "text", + "clientId": "client-text-1", + "text": "hello", + "text_elements": [], + }) + ); + + let decoded = serde_json::from_value::(value).expect("deserialize user input"); + assert_eq!(decoded, input); + + let input_without_client_id = UserInput::Text { + client_id: None, + text: "hello".to_string(), + text_elements: Vec::new(), + }; + + assert_eq!( + serde_json::to_value(&input_without_client_id).expect("serialize user input"), + json!({ + "type": "text", + "clientId": null, + "text": "hello", + "text_elements": [], + }) + ); + + assert_eq!( + serde_json::from_value::(json!({ + "type": "text", + "text": "hello", + "text_elements": [], + })) + .expect("deserialize user input without client id"), + input_without_client_id + ); +} + #[test] fn user_input_into_core_preserves_image_detail() { assert_eq!( UserInput::Image { + client_id: Some("client-image-1".to_string()), url: "https://example.com/image.png".to_string(), detail: Some(ImageDetail::Original), } .into_core(), CoreUserInput::Image { + client_id: Some("client-image-1".to_string()), image_url: "https://example.com/image.png".to_string(), detail: Some(ImageDetail::Original), } @@ -2588,11 +2649,13 @@ fn user_input_into_core_preserves_image_detail() { assert_eq!( UserInput::LocalImage { + client_id: Some("client-local-image-1".to_string()), path: PathBuf::from("local/image.png"), detail: Some(ImageDetail::Original), } .into_core(), CoreUserInput::LocalImage { + client_id: Some("client-local-image-1".to_string()), path: PathBuf::from("local/image.png"), detail: Some(ImageDetail::Original), } diff --git a/codex-rs/app-server-protocol/src/protocol/v2/turn.rs b/codex-rs/app-server-protocol/src/protocol/v2/turn.rs index ab5e59a4644..b31597bf040 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2/turn.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2/turn.rs @@ -266,28 +266,43 @@ impl From for CoreTextElement { #[ts(export_to = "v2/")] pub enum UserInput { Text { + #[serde(rename = "clientId", default)] + #[ts(rename = "clientId", optional)] + client_id: Option, text: String, /// UI-defined spans within `text` used to render or persist special elements. #[serde(default)] text_elements: Vec, }, Image { + #[serde(rename = "clientId", default)] + #[ts(rename = "clientId", optional)] + client_id: Option, #[serde(default)] #[ts(optional)] detail: Option, url: String, }, LocalImage { + #[serde(rename = "clientId", default)] + #[ts(rename = "clientId", optional)] + client_id: Option, #[serde(default)] #[ts(optional)] detail: Option, path: PathBuf, }, Skill { + #[serde(rename = "clientId", default)] + #[ts(rename = "clientId", optional)] + client_id: Option, name: String, path: PathBuf, }, Mention { + #[serde(rename = "clientId", default)] + #[ts(rename = "clientId", optional)] + client_id: Option, name: String, path: String, }, @@ -297,19 +312,50 @@ impl UserInput { pub fn into_core(self) -> CoreUserInput { match self { UserInput::Text { + client_id, text, text_elements, } => CoreUserInput::Text { + client_id, text, text_elements: text_elements.into_iter().map(Into::into).collect(), }, - UserInput::Image { url, detail } => CoreUserInput::Image { + UserInput::Image { + client_id, + url, + detail, + } => CoreUserInput::Image { + client_id, image_url: url, detail, }, - UserInput::LocalImage { path, detail } => CoreUserInput::LocalImage { path, detail }, - UserInput::Skill { name, path } => CoreUserInput::Skill { name, path }, - UserInput::Mention { name, path } => CoreUserInput::Mention { name, path }, + UserInput::LocalImage { + client_id, + path, + detail, + } => CoreUserInput::LocalImage { + client_id, + path, + detail, + }, + UserInput::Skill { + client_id, + name, + path, + } => CoreUserInput::Skill { + client_id, + name, + path, + }, + UserInput::Mention { + client_id, + name, + path, + } => CoreUserInput::Mention { + client_id, + name, + path, + }, } } } @@ -318,19 +364,50 @@ impl From for UserInput { fn from(value: CoreUserInput) -> Self { match value { CoreUserInput::Text { + client_id, text, text_elements, } => UserInput::Text { + client_id, text, text_elements: text_elements.into_iter().map(Into::into).collect(), }, - CoreUserInput::Image { image_url, detail } => UserInput::Image { + CoreUserInput::Image { + client_id, + image_url, + detail, + } => UserInput::Image { + client_id, url: image_url, detail, }, - CoreUserInput::LocalImage { path, detail } => UserInput::LocalImage { path, detail }, - CoreUserInput::Skill { name, path } => UserInput::Skill { name, path }, - CoreUserInput::Mention { name, path } => UserInput::Mention { name, path }, + CoreUserInput::LocalImage { + client_id, + path, + detail, + } => UserInput::LocalImage { + client_id, + path, + detail, + }, + CoreUserInput::Skill { + client_id, + name, + path, + } => UserInput::Skill { + client_id, + name, + path, + }, + CoreUserInput::Mention { + client_id, + name, + path, + } => UserInput::Mention { + client_id, + name, + path, + }, _ => unreachable!("unsupported user input variant"), } } diff --git a/codex-rs/protocol/src/items.rs b/codex-rs/protocol/src/items.rs index 7d12579564b..ed616d6c553 100644 --- a/codex-rs/protocol/src/items.rs +++ b/codex-rs/protocol/src/items.rs @@ -272,6 +272,7 @@ impl UserMessageItem { if let UserInput::Text { text, text_elements, + .. } = input { // Text element ranges are relative to each text chunk; offset them so they align diff --git a/codex-rs/protocol/src/models.rs b/codex-rs/protocol/src/models.rs index 86603db1efb..aa93d44765d 100644 --- a/codex-rs/protocol/src/models.rs +++ b/codex-rs/protocol/src/models.rs @@ -1237,7 +1237,9 @@ impl From> for ResponseInputItem { .into_iter() .flat_map(|c| match c { UserInput::Text { text, .. } => vec![ContentItem::InputText { text }], - UserInput::Image { image_url, detail } => { + UserInput::Image { + image_url, detail, .. + } => { image_index += 1; let detail = detail.unwrap_or(DEFAULT_IMAGE_DETAIL); vec![ContentItem::InputImage { @@ -1245,7 +1247,7 @@ impl From> for ResponseInputItem { detail: Some(detail), }] } - UserInput::LocalImage { path, detail } => { + UserInput::LocalImage { path, detail, .. } => { image_index += 1; let detail = detail.unwrap_or(DEFAULT_IMAGE_DETAIL); match std::fs::read(&path) { @@ -2652,6 +2654,7 @@ mod tests { let image_url = "data:image/png;base64,abc".to_string(); let item = ResponseInputItem::from(vec![UserInput::Image { + client_id: None, image_url: image_url.clone(), detail: None, }]); @@ -2675,6 +2678,7 @@ mod tests { let image_url = "data:image/png;base64,abc".to_string(); let item = ResponseInputItem::from(vec![UserInput::Image { + client_id: None, image_url: image_url.clone(), detail: Some(ImageDetail::Original), }]); @@ -2867,10 +2871,12 @@ mod tests { let item = ResponseInputItem::from(vec![ UserInput::Image { + client_id: None, image_url: image_url.clone(), detail: None, }, UserInput::LocalImage { + client_id: None, path: local_path, detail: None, }, @@ -2915,6 +2921,7 @@ mod tests { std::fs::write(&local_path, TINY_PNG_BYTES)?; let item = ResponseInputItem::from(vec![UserInput::LocalImage { + client_id: None, path: local_path, detail: Some(ImageDetail::Original), }]); @@ -2941,6 +2948,7 @@ mod tests { let missing_path = dir.path().join("missing-image.png"); let item = ResponseInputItem::from(vec![UserInput::LocalImage { + client_id: None, path: missing_path.clone(), detail: None, }]); @@ -2976,6 +2984,7 @@ mod tests { std::fs::write(&json_path, br#"{"hello":"world"}"#)?; let item = ResponseInputItem::from(vec![UserInput::LocalImage { + client_id: None, path: json_path.clone(), detail: None, }]); @@ -3014,6 +3023,7 @@ mod tests { )?; let item = ResponseInputItem::from(vec![UserInput::LocalImage { + client_id: None, path: svg_path.clone(), detail: None, }]); diff --git a/codex-rs/protocol/src/protocol.rs b/codex-rs/protocol/src/protocol.rs index a675362b621..201ed582991 100644 --- a/codex-rs/protocol/src/protocol.rs +++ b/codex-rs/protocol/src/protocol.rs @@ -5068,6 +5068,7 @@ mod tests { #[test] fn user_input_text_serializes_empty_text_elements() -> Result<()> { let input = UserInput::Text { + client_id: None, text: "hello".to_string(), text_elements: Vec::new(), }; @@ -5135,14 +5136,17 @@ mod tests { let local_path = PathBuf::from("/tmp/local.png"); let item = UserMessageItem::new(&[ crate::user_input::UserInput::Image { + client_id: None, image_url: "https://example.com/first.png".to_string(), detail: Some(ImageDetail::Original), }, crate::user_input::UserInput::Image { + client_id: None, image_url: "https://example.com/second.png".to_string(), detail: None, }, crate::user_input::UserInput::LocalImage { + client_id: None, path: local_path.clone(), detail: Some(ImageDetail::Original), }, diff --git a/codex-rs/protocol/src/user_input.rs b/codex-rs/protocol/src/user_input.rs index ce4cf99eba9..b9a50f25aea 100644 --- a/codex-rs/protocol/src/user_input.rs +++ b/codex-rs/protocol/src/user_input.rs @@ -14,6 +14,8 @@ pub const MAX_USER_INPUT_TEXT_CHARS: usize = 1 << 20; #[serde(tag = "type", rename_all = "snake_case")] pub enum UserInput { Text { + #[serde(default, skip_serializing_if = "Option::is_none")] + client_id: Option, text: String, /// UI-defined spans within `text` that should be treated as special elements. /// These are byte ranges into the UTF-8 `text` buffer and are used to render @@ -24,6 +26,8 @@ pub enum UserInput { }, /// Pre‑encoded data: URI image. Image { + #[serde(default, skip_serializing_if = "Option::is_none")] + client_id: Option, image_url: String, #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional)] @@ -33,6 +37,8 @@ pub enum UserInput { /// Local image path provided by the user. This will be converted to an /// `Image` variant (base64 data URL) during request serialization. LocalImage { + #[serde(default, skip_serializing_if = "Option::is_none")] + client_id: Option, path: std::path::PathBuf, #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional)] @@ -41,6 +47,8 @@ pub enum UserInput { /// Skill selected by the user (name + path to SKILL.md). Skill { + #[serde(default, skip_serializing_if = "Option::is_none")] + client_id: Option, name: String, path: std::path::PathBuf, }, @@ -48,7 +56,12 @@ pub enum UserInput { /// /// `path` identifies the exact mention target, for example /// `app://` or `plugin://@`. - Mention { name: String, path: String }, + Mention { + #[serde(default, skip_serializing_if = "Option::is_none")] + client_id: Option, + name: String, + path: String, + }, } #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, TS, JsonSchema)] From 802027e5d0c56af8e5087f7c24a5e4423b918609 Mon Sep 17 00:00:00 2001 From: Alexi Christakis <167903946+alexi-openai@users.noreply.github.com> Date: Wed, 27 May 2026 00:55:03 +0000 Subject: [PATCH 2/6] Fix user input client id call sites --- .../analytics/src/analytics_client_tests.rs | 4 ++ codex-rs/app-server-test-client/src/lib.rs | 6 ++ .../src/message_processor_tracing_tests.rs | 1 + .../thread_processor_tests.rs | 1 + .../src/request_processors/turn_processor.rs | 1 + codex-rs/app-server/tests/suite/v2/account.rs | 4 ++ .../app-server/tests/suite/v2/attestation.rs | 1 + .../tests/suite/v2/client_metadata.rs | 5 ++ .../app-server/tests/suite/v2/compaction.rs | 1 + .../v2/connection_handling_websocket_unix.rs | 1 + .../tests/suite/v2/dynamic_tools.rs | 4 ++ .../tests/suite/v2/external_agent_config.rs | 2 + .../app-server/tests/suite/v2/hooks_list.rs | 5 ++ .../app-server/tests/suite/v2/initialize.rs | 1 + .../tests/suite/v2/mcp_server_elicitation.rs | 2 + .../app-server/tests/suite/v2/mcp_tool.rs | 1 + .../tests/suite/v2/output_schema.rs | 3 + .../app-server/tests/suite/v2/plan_item.rs | 1 + .../tests/suite/v2/remote_thread_store.rs | 1 + .../tests/suite/v2/request_permissions.rs | 1 + .../tests/suite/v2/request_user_input.rs | 1 + codex-rs/app-server/tests/suite/v2/review.rs | 4 ++ .../tests/suite/v2/safety_check_downgrade.rs | 4 ++ .../tests/suite/v2/thread_archive.rs | 3 + .../app-server/tests/suite/v2/thread_fork.rs | 3 + .../tests/suite/v2/thread_inject_items.rs | 3 + .../app-server/tests/suite/v2/thread_list.rs | 2 + .../app-server/tests/suite/v2/thread_read.rs | 2 + .../tests/suite/v2/thread_resume.rs | 25 ++++++++ .../tests/suite/v2/thread_rollback.rs | 4 ++ .../tests/suite/v2/thread_settings_update.rs | 2 + .../tests/suite/v2/thread_shell_command.rs | 1 + .../tests/suite/v2/thread_status.rs | 2 + .../tests/suite/v2/thread_unarchive.rs | 1 + .../tests/suite/v2/thread_unsubscribe.rs | 2 + .../tests/suite/v2/turn_interrupt.rs | 3 + .../app-server/tests/suite/v2/turn_start.rs | 41 +++++++++++++ .../tests/suite/v2/turn_start_zsh_fork.rs | 4 ++ .../app-server/tests/suite/v2/turn_steer.rs | 6 ++ .../app-server/tests/suite/v2/web_search.rs | 1 + codex-rs/cli/src/main.rs | 7 ++- codex-rs/core-skills/src/injection.rs | 2 +- codex-rs/core-skills/src/injection_tests.rs | 16 ++++++ codex-rs/core/src/agent/control.rs | 6 +- codex-rs/core/src/agent/control_tests.rs | 7 +++ codex-rs/core/src/compact.rs | 1 + codex-rs/core/src/event_mapping.rs | 2 + codex-rs/core/src/event_mapping_tests.rs | 7 +++ codex-rs/core/src/guardian/prompt.rs | 1 + codex-rs/core/src/plugins/mentions_tests.rs | 7 +++ codex-rs/core/src/session/mod.rs | 1 + codex-rs/core/src/session/review.rs | 1 + codex-rs/core/src/session/tests.rs | 26 +++++++++ codex-rs/core/src/tasks/compact.rs | 1 + .../core/src/tools/handlers/agent_jobs.rs | 1 + .../src/tools/handlers/multi_agents_common.rs | 1 + .../src/tools/handlers/multi_agents_tests.rs | 6 ++ codex-rs/core/tests/common/test_codex.rs | 1 + codex-rs/core/tests/suite/abort_tasks.rs | 5 ++ .../core/tests/suite/additional_context.rs | 10 ++++ codex-rs/core/tests/suite/apply_patch_cli.rs | 1 + codex-rs/core/tests/suite/approvals.rs | 2 + codex-rs/core/tests/suite/client.rs | 34 +++++++++++ .../core/tests/suite/client_websockets.rs | 2 + codex-rs/core/tests/suite/code_mode.rs | 1 + .../tests/suite/collaboration_instructions.rs | 17 ++++++ codex-rs/core/tests/suite/compact.rs | 33 +++++++++++ codex-rs/core/tests/suite/compact_remote.rs | 57 +++++++++++++++++++ .../core/tests/suite/compact_remote_parity.rs | 7 +++ .../core/tests/suite/compact_resume_fork.rs | 1 + codex-rs/core/tests/suite/exec_policy.rs | 2 + codex-rs/core/tests/suite/fork_thread.rs | 2 + codex-rs/core/tests/suite/hooks.rs | 2 + codex-rs/core/tests/suite/image_rollout.rs | 4 ++ codex-rs/core/tests/suite/items.rs | 10 ++++ codex-rs/core/tests/suite/json_result.rs | 1 + .../core/tests/suite/mcp_turn_metadata.rs | 1 + codex-rs/core/tests/suite/model_switching.rs | 15 +++++ .../core/tests/suite/model_visible_layout.rs | 8 +++ codex-rs/core/tests/suite/models_cache_ttl.rs | 1 + .../core/tests/suite/models_etag_responses.rs | 1 + codex-rs/core/tests/suite/otel.rs | 22 +++++++ codex-rs/core/tests/suite/pending_input.rs | 5 ++ .../core/tests/suite/permissions_messages.rs | 15 +++++ codex-rs/core/tests/suite/personality.rs | 1 + codex-rs/core/tests/suite/plugins.rs | 3 + codex-rs/core/tests/suite/prompt_caching.rs | 15 +++++ .../core/tests/suite/prompt_debug_tests.rs | 1 + codex-rs/core/tests/suite/quota_exceeded.rs | 1 + .../core/tests/suite/realtime_conversation.rs | 3 + codex-rs/core/tests/suite/remote_env.rs | 1 + codex-rs/core/tests/suite/remote_models.rs | 7 +++ .../core/tests/suite/request_compression.rs | 2 + .../core/tests/suite/request_permissions.rs | 1 + .../tests/suite/request_permissions_tool.rs | 1 + .../core/tests/suite/request_user_input.rs | 3 + .../suite/responses_api_proxy_headers.rs | 1 + codex-rs/core/tests/suite/resume.rs | 7 +++ codex-rs/core/tests/suite/review.rs | 1 + codex-rs/core/tests/suite/rmcp_client.rs | 1 + .../tests/suite/safety_check_downgrade.rs | 1 + codex-rs/core/tests/suite/search_tool.rs | 4 ++ codex-rs/core/tests/suite/shell_snapshot.rs | 4 ++ codex-rs/core/tests/suite/skill_approval.rs | 1 + codex-rs/core/tests/suite/skills.rs | 2 + codex-rs/core/tests/suite/sqlite_state.rs | 1 + .../suite/stream_error_allows_next_turn.rs | 2 + .../core/tests/suite/stream_no_completed.rs | 1 + .../tests/suite/subagent_notifications.rs | 1 + codex-rs/core/tests/suite/tool_harness.rs | 5 ++ codex-rs/core/tests/suite/tool_parallelism.rs | 2 + codex-rs/core/tests/suite/truncation.rs | 1 + codex-rs/core/tests/suite/unified_exec.rs | 7 +++ .../core/tests/suite/user_notification.rs | 1 + codex-rs/core/tests/suite/user_shell_cmd.rs | 1 + codex-rs/core/tests/suite/view_image.rs | 12 ++++ .../core/tests/suite/websocket_fallback.rs | 1 + codex-rs/core/tests/suite/window_headers.rs | 1 + codex-rs/debug-client/src/client.rs | 1 + codex-rs/exec/src/lib.rs | 14 ++++- codex-rs/mcp-server/src/codex_tool_runner.rs | 2 + codex-rs/memories/write/src/phase2.rs | 1 + .../tests/suite/otel_export_routing_policy.rs | 3 + codex-rs/thread-manager-sample/src/main.rs | 1 + codex-rs/tui/src/app/tests.rs | 10 ++++ codex-rs/tui/src/app/tests/startup.rs | 1 + codex-rs/tui/src/app_server_session.rs | 1 + .../tui/src/chatwidget/input_submission.rs | 8 +++ .../tui/src/chatwidget/tests/app_server.rs | 1 + .../chatwidget/tests/composer_submission.rs | 16 ++++++ .../tui/src/chatwidget/tests/exec_flow.rs | 1 + .../src/chatwidget/tests/goal_validation.rs | 1 + codex-rs/tui/src/chatwidget/tests/helpers.rs | 2 + .../src/chatwidget/tests/history_replay.rs | 6 ++ .../tui/src/chatwidget/tests/plan_mode.rs | 7 +++ .../tui/src/chatwidget/tests/review_mode.rs | 16 ++++++ codex-rs/tui/src/chatwidget/tests/side.rs | 1 + .../src/chatwidget/tests/slash_commands.rs | 8 +++ .../src/chatwidget/tests/status_and_layout.rs | 1 + codex-rs/tui/src/chatwidget/user_messages.rs | 1 + codex-rs/tui/src/ide_context/prompt.rs | 8 +++ codex-rs/tui/src/resume_picker.rs | 1 + 142 files changed, 716 insertions(+), 6 deletions(-) diff --git a/codex-rs/analytics/src/analytics_client_tests.rs b/codex-rs/analytics/src/analytics_client_tests.rs index 48379cc39b4..7c76cf3e779 100644 --- a/codex-rs/analytics/src/analytics_client_tests.rs +++ b/codex-rs/analytics/src/analytics_client_tests.rs @@ -274,10 +274,12 @@ fn sample_turn_start_request(thread_id: &str, request_id: i64) -> ClientRequest thread_id: thread_id.to_string(), input: vec![ UserInput::Text { + client_id: None, text: "hello".to_string(), text_elements: vec![], }, UserInput::Image { + client_id: None, url: "https://example.com/a.png".to_string(), detail: None, }, @@ -393,10 +395,12 @@ fn sample_turn_steer_request( expected_turn_id: expected_turn_id.to_string(), input: vec![ UserInput::Text { + client_id: None, text: "more".to_string(), text_elements: vec![], }, UserInput::LocalImage { + client_id: None, path: "/tmp/a.png".into(), detail: None, }, diff --git a/codex-rs/app-server-test-client/src/lib.rs b/codex-rs/app-server-test-client/src/lib.rs index fd9ecc845dd..599edfca90c 100644 --- a/codex-rs/app-server-test-client/src/lib.rs +++ b/codex-rs/app-server-test-client/src/lib.rs @@ -735,6 +735,7 @@ async fn trigger_zsh_fork_multi_cmd_approval( let mut turn_params = TurnStartParams { thread_id: thread_response.thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: message, text_elements: Vec::new(), }], @@ -819,6 +820,7 @@ async fn resume_message_v2( let turn_response = client.turn_start(TurnStartParams { thread_id: resume_response.thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: user_message, text_elements: Vec::new(), }], @@ -960,6 +962,7 @@ async fn send_message_v2_with_policies( let mut turn_params = TurnStartParams { thread_id: thread_response.thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: user_message, // Test client sends plain text without UI element ranges. text_elements: Vec::new(), @@ -1000,6 +1003,7 @@ async fn send_follow_up_v2( let first_turn_params = TurnStartParams { thread_id: thread_response.thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: first_message, // Test client sends plain text without UI element ranges. text_elements: Vec::new(), @@ -1013,6 +1017,7 @@ async fn send_follow_up_v2( let follow_up_params = TurnStartParams { thread_id: thread_response.thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: follow_up_message, // Test client sends plain text without UI element ranges. text_elements: Vec::new(), @@ -1256,6 +1261,7 @@ fn live_elicitation_timeout_pause( let turn_response = client.turn_start(TurnStartParams { thread_id: thread_id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: prompt, text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/src/message_processor_tracing_tests.rs b/codex-rs/app-server/src/message_processor_tracing_tests.rs index 97c497b8771..0702596cc50 100644 --- a/codex-rs/app-server/src/message_processor_tracing_tests.rs +++ b/codex-rs/app-server/src/message_processor_tracing_tests.rs @@ -654,6 +654,7 @@ async fn turn_start_jsonrpc_span_parents_core_turn_spans() -> Result<()> { environments: None, thread_id, input: vec![UserInput::Text { + client_id: None, text: "hello".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/src/request_processors/thread_processor_tests.rs b/codex-rs/app-server/src/request_processors/thread_processor_tests.rs index 2fa95b3d237..e2fbbefacee 100644 --- a/codex-rs/app-server/src/request_processors/thread_processor_tests.rs +++ b/codex-rs/app-server/src/request_processors/thread_processor_tests.rs @@ -225,6 +225,7 @@ mod thread_processor_behavior_tests { items: vec![ThreadItem::UserMessage { id: "live-user-message".to_string(), content: vec![V2UserInput::Text { + client_id: None, text: "live".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/src/request_processors/turn_processor.rs b/codex-rs/app-server/src/request_processors/turn_processor.rs index 975ff22d071..e41628a606a 100644 --- a/codex-rs/app-server/src/request_processors/turn_processor.rs +++ b/codex-rs/app-server/src/request_processors/turn_processor.rs @@ -990,6 +990,7 @@ impl TurnRequestProcessor { vec![ThreadItem::UserMessage { id: turn_id.clone(), content: vec![V2UserInput::Text { + client_id: None, text: display_text.to_string(), // Review prompt display text is synthesized; no UI element ranges to preserve. text_elements: Vec::new(), diff --git a/codex-rs/app-server/tests/suite/v2/account.rs b/codex-rs/app-server/tests/suite/v2/account.rs index a15b46abd9d..fa6813c50b2 100644 --- a/codex-rs/app-server/tests/suite/v2/account.rs +++ b/codex-rs/app-server/tests/suite/v2/account.rs @@ -507,6 +507,7 @@ async fn external_auth_refreshes_on_unauthorized() -> Result<()> { .send_turn_start_request(codex_app_server_protocol::TurnStartParams { thread_id: thread.thread.id, input: vec![codex_app_server_protocol::UserInput::Text { + client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], @@ -613,6 +614,7 @@ async fn external_auth_refresh_error_fails_turn() -> Result<()> { .send_turn_start_request(codex_app_server_protocol::TurnStartParams { thread_id: thread.thread.id.clone(), input: vec![codex_app_server_protocol::UserInput::Text { + client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], @@ -735,6 +737,7 @@ async fn external_auth_refresh_mismatched_workspace_fails_turn() -> Result<()> { .send_turn_start_request(codex_app_server_protocol::TurnStartParams { thread_id: thread.thread.id.clone(), input: vec![codex_app_server_protocol::UserInput::Text { + client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], @@ -850,6 +853,7 @@ async fn external_auth_refresh_invalid_access_token_fails_turn() -> Result<()> { .send_turn_start_request(codex_app_server_protocol::TurnStartParams { thread_id: thread.thread.id.clone(), input: vec![codex_app_server_protocol::UserInput::Text { + client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/attestation.rs b/codex-rs/app-server/tests/suite/v2/attestation.rs index d0565e2571d..8a11be65a44 100644 --- a/codex-rs/app-server/tests/suite/v2/attestation.rs +++ b/codex-rs/app-server/tests/suite/v2/attestation.rs @@ -111,6 +111,7 @@ async fn attestation_generate_round_trip_adds_header_to_responses_websocket_hand .send_turn_start_request(TurnStartParams { thread_id: thread.id, input: vec![V2UserInput::Text { + client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/client_metadata.rs b/codex-rs/app-server/tests/suite/v2/client_metadata.rs index 9daa8d34f17..7c6b3409e0e 100644 --- a/codex-rs/app-server/tests/suite/v2/client_metadata.rs +++ b/codex-rs/app-server/tests/suite/v2/client_metadata.rs @@ -74,6 +74,7 @@ async fn turn_start_forwards_client_metadata_to_responses_request_v2() -> Result .send_turn_start_request(TurnStartParams { thread_id: thread.id, input: vec![V2UserInput::Text { + client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], @@ -154,6 +155,7 @@ async fn turn_start_sends_fork_lineage_in_turn_metadata_for_thread_fork_v2() -> .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "Continue".to_string(), text_elements: Vec::new(), }], @@ -334,6 +336,7 @@ async fn turn_steer_updates_client_metadata_on_follow_up_responses_request_v2() .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "Run sleep".to_string(), text_elements: Vec::new(), }], @@ -364,6 +367,7 @@ async fn turn_steer_updates_client_metadata_on_follow_up_responses_request_v2() .send_turn_steer_request(TurnSteerParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "Focus on the failure".to_string(), text_elements: Vec::new(), }], @@ -459,6 +463,7 @@ async fn turn_start_forwards_client_metadata_to_responses_websocket_request_body .send_turn_start_request(TurnStartParams { thread_id: thread.id, input: vec![V2UserInput::Text { + client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/compaction.rs b/codex-rs/app-server/tests/suite/v2/compaction.rs index 2cf9f6b36ce..c4f9bda8987 100644 --- a/codex-rs/app-server/tests/suite/v2/compaction.rs +++ b/codex-rs/app-server/tests/suite/v2/compaction.rs @@ -395,6 +395,7 @@ async fn send_turn_and_wait(mcp: &mut McpProcess, thread_id: &str, text: &str) - .send_turn_start_request(TurnStartParams { thread_id: thread_id.to_string(), input: vec![V2UserInput::Text { + client_id: None, text: text.to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/connection_handling_websocket_unix.rs b/codex-rs/app-server/tests/suite/v2/connection_handling_websocket_unix.rs index 4b708d4ab3b..01d4c8e5123 100644 --- a/codex-rs/app-server/tests/suite/v2/connection_handling_websocket_unix.rs +++ b/codex-rs/app-server/tests/suite/v2/connection_handling_websocket_unix.rs @@ -227,6 +227,7 @@ async fn send_turn_start_request(stream: &mut WsClient, id: i64, thread_id: &str Some(serde_json::to_value(TurnStartParams { thread_id: thread_id.to_string(), input: vec![V2UserInput::Text { + client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/dynamic_tools.rs b/codex-rs/app-server/tests/suite/v2/dynamic_tools.rs index b357e139db5..ac8edec221c 100644 --- a/codex-rs/app-server/tests/suite/v2/dynamic_tools.rs +++ b/codex-rs/app-server/tests/suite/v2/dynamic_tools.rs @@ -90,6 +90,7 @@ async fn thread_start_injects_dynamic_tools_into_model_requests() -> Result<()> .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], @@ -169,6 +170,7 @@ async fn thread_start_keeps_hidden_dynamic_tools_out_of_model_requests() -> Resu .send_turn_start_request(TurnStartParams { thread_id: thread.id, input: vec![V2UserInput::Text { + client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], @@ -348,6 +350,7 @@ async fn dynamic_tool_call_round_trip_sends_text_content_items_to_model() -> Res .send_turn_start_request(TurnStartParams { thread_id: thread_id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "Run the tool".to_string(), text_elements: Vec::new(), }], @@ -522,6 +525,7 @@ async fn dynamic_tool_call_round_trip_sends_content_items_to_model() -> Result<( .send_turn_start_request(TurnStartParams { thread_id: thread_id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "Run the tool".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/external_agent_config.rs b/codex-rs/app-server/tests/suite/v2/external_agent_config.rs index f5f74c0231b..f42070e1c37 100644 --- a/codex-rs/app-server/tests/suite/v2/external_agent_config.rs +++ b/codex-rs/app-server/tests/suite/v2/external_agent_config.rs @@ -392,6 +392,7 @@ async fn external_agent_config_import_creates_session_rollouts() -> Result<()> { .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![UserInput::Text { + client_id: None, text: "follow up".to_string(), text_elements: Vec::new(), }], @@ -965,6 +966,7 @@ async fn external_agent_config_import_compacts_huge_session_before_first_follow_ .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![UserInput::Text { + client_id: None, text: "follow up".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/hooks_list.rs b/codex-rs/app-server/tests/suite/v2/hooks_list.rs index 1c8f76fe51c..1c5e6736f82 100644 --- a/codex-rs/app-server/tests/suite/v2/hooks_list.rs +++ b/codex-rs/app-server/tests/suite/v2/hooks_list.rs @@ -663,6 +663,7 @@ command = "python3 {hook_script_path}" .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "first turn".to_string(), text_elements: Vec::new(), }], @@ -724,6 +725,7 @@ command = "python3 {hook_script_path}" .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "second turn".to_string(), text_elements: Vec::new(), }], @@ -793,6 +795,7 @@ command = "python3 {hook_script_path}" .send_turn_start_request(TurnStartParams { thread_id: thread.id, input: vec![V2UserInput::Text { + client_id: None, text: "third turn".to_string(), text_elements: Vec::new(), }], @@ -933,6 +936,7 @@ command = "python3 {hook_script_path}" .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "first turn".to_string(), text_elements: Vec::new(), }], @@ -984,6 +988,7 @@ command = "python3 {hook_script_path}" .send_turn_start_request(TurnStartParams { thread_id: thread.id, input: vec![V2UserInput::Text { + client_id: None, text: "second turn".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/initialize.rs b/codex-rs/app-server/tests/suite/v2/initialize.rs index 47cacdb20e2..75d0edfb226 100644 --- a/codex-rs/app-server/tests/suite/v2/initialize.rs +++ b/codex-rs/app-server/tests/suite/v2/initialize.rs @@ -307,6 +307,7 @@ async fn turn_start_notify_payload_includes_initialize_client_name() -> Result<( .send_turn_start_request(TurnStartParams { thread_id: thread.id, input: vec![V2UserInput::Text { + client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/mcp_server_elicitation.rs b/codex-rs/app-server/tests/suite/v2/mcp_server_elicitation.rs index 9c415a4c9e5..18e3d253df2 100644 --- a/codex-rs/app-server/tests/suite/v2/mcp_server_elicitation.rs +++ b/codex-rs/app-server/tests/suite/v2/mcp_server_elicitation.rs @@ -136,6 +136,7 @@ async fn mcp_server_elicitation_round_trip() -> Result<()> { .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "Warm up connectors.".to_string(), text_elements: Vec::new(), }], @@ -168,6 +169,7 @@ async fn mcp_server_elicitation_round_trip() -> Result<()> { .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "Use [$calendar](app://calendar) to run the calendar tool.".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/mcp_tool.rs b/codex-rs/app-server/tests/suite/v2/mcp_tool.rs index eff9ec9603a..809fcf24371 100644 --- a/codex-rs/app-server/tests/suite/v2/mcp_tool.rs +++ b/codex-rs/app-server/tests/suite/v2/mcp_tool.rs @@ -462,6 +462,7 @@ url = "{mcp_server_url}/mcp" .send_turn_start_request(TurnStartParams { thread_id: thread.id, input: vec![V2UserInput::Text { + client_id: None, text: "Call the large MCP tool".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/output_schema.rs b/codex-rs/app-server/tests/suite/v2/output_schema.rs index 149e098b686..f2ab19c417d 100644 --- a/codex-rs/app-server/tests/suite/v2/output_schema.rs +++ b/codex-rs/app-server/tests/suite/v2/output_schema.rs @@ -60,6 +60,7 @@ async fn turn_start_accepts_output_schema_v2() -> Result<()> { .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], @@ -142,6 +143,7 @@ async fn turn_start_output_schema_is_per_turn_v2() -> Result<()> { .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], @@ -184,6 +186,7 @@ async fn turn_start_output_schema_is_per_turn_v2() -> Result<()> { .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "Hello again".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/plan_item.rs b/codex-rs/app-server/tests/suite/v2/plan_item.rs index 97e67fa090c..7681b21ef2b 100644 --- a/codex-rs/app-server/tests/suite/v2/plan_item.rs +++ b/codex-rs/app-server/tests/suite/v2/plan_item.rs @@ -153,6 +153,7 @@ async fn start_plan_mode_turn(mcp: &mut McpProcess) -> Result Result<()> { .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "pick a directory".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/request_user_input.rs b/codex-rs/app-server/tests/suite/v2/request_user_input.rs index f77ddfb4f7d..ce73f23a020 100644 --- a/codex-rs/app-server/tests/suite/v2/request_user_input.rs +++ b/codex-rs/app-server/tests/suite/v2/request_user_input.rs @@ -52,6 +52,7 @@ async fn request_user_input_round_trip() -> Result<()> { .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "ask something".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/review.rs b/codex-rs/app-server/tests/suite/v2/review.rs index bf0271f8217..27f5bedbf04 100644 --- a/codex-rs/app-server/tests/suite/v2/review.rs +++ b/codex-rs/app-server/tests/suite/v2/review.rs @@ -92,6 +92,7 @@ async fn review_start_runs_review_turn_and_emits_code_review_item() -> Result<() vec![ThreadItem::UserMessage { id: turn_id.clone(), content: vec![V2UserInput::Text { + client_id: None, text: "commit 1234567: Tidy UI colors".to_string(), text_elements: Vec::new(), }], @@ -200,6 +201,7 @@ async fn review_start_exec_approval_item_id_matches_command_execution_item() -> vec![ThreadItem::UserMessage { id: turn_id.clone(), content: vec![V2UserInput::Text { + client_id: None, text: "commit 1234567: Check review approvals".to_string(), text_elements: Vec::new(), }], @@ -329,6 +331,7 @@ async fn review_start_with_detached_delivery_returns_new_thread_id() -> Result<( vec![ThreadItem::UserMessage { id: turn.id.clone(), content: vec![V2UserInput::Text { + client_id: None, text: "detached review".to_string(), text_elements: Vec::new(), }], @@ -466,6 +469,7 @@ async fn materialize_thread_rollout(mcp: &mut McpProcess, thread_id: &str) -> Re .send_turn_start_request(TurnStartParams { thread_id: thread_id.to_string(), input: vec![V2UserInput::Text { + client_id: None, text: "materialize rollout".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/safety_check_downgrade.rs b/codex-rs/app-server/tests/suite/v2/safety_check_downgrade.rs index 7c1619e38a9..7f5b2063870 100644 --- a/codex-rs/app-server/tests/suite/v2/safety_check_downgrade.rs +++ b/codex-rs/app-server/tests/suite/v2/safety_check_downgrade.rs @@ -68,6 +68,7 @@ async fn openai_model_header_mismatch_emits_model_rerouted_notification_v2() -> .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![UserInput::Text { + client_id: None, text: "trigger safeguard".to_string(), text_elements: Vec::new(), }], @@ -134,6 +135,7 @@ async fn cyber_policy_response_emits_typed_error_notification_v2() -> Result<()> .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![UserInput::Text { + client_id: None, text: "trigger cyber policy error".to_string(), text_elements: Vec::new(), }], @@ -210,6 +212,7 @@ async fn response_model_field_mismatch_emits_model_rerouted_notification_v2_when .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![UserInput::Text { + client_id: None, text: "trigger response model check".to_string(), text_elements: Vec::new(), }], @@ -278,6 +281,7 @@ async fn model_verification_emits_typed_notification_and_warning_v2() -> Result< .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![UserInput::Text { + client_id: None, text: "trigger model verification".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/thread_archive.rs b/codex-rs/app-server/tests/suite/v2/thread_archive.rs index b441a23cb62..d01cc7c7b10 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_archive.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_archive.rs @@ -94,6 +94,7 @@ async fn thread_archive_requires_materialized_rollout() -> Result<()> { .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![UserInput::Text { + client_id: None, text: "materialize".to_string(), text_elements: Vec::new(), }], @@ -518,6 +519,7 @@ async fn thread_archive_clears_stale_subscriptions_before_resume() -> Result<()> .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![UserInput::Text { + client_id: None, text: "materialize".to_string(), text_elements: Vec::new(), }], @@ -595,6 +597,7 @@ async fn thread_archive_clears_stale_subscriptions_before_resume() -> Result<()> .send_turn_start_request(TurnStartParams { thread_id: thread.id, input: vec![UserInput::Text { + client_id: None, text: "secondary turn".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/thread_fork.rs b/codex-rs/app-server/tests/suite/v2/thread_fork.rs index 287afe1016c..8965c1c6d65 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_fork.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_fork.rs @@ -155,6 +155,7 @@ async fn thread_fork_creates_new_thread_and_emits_started() -> Result<()> { assert_eq!( content, &vec![UserInput::Text { + client_id: None, text: preview.to_string(), text_elements: Vec::new(), }] @@ -653,6 +654,7 @@ async fn thread_fork_ephemeral_remains_pathless_and_omits_listing() -> Result<() assert_eq!( content, &vec![UserInput::Text { + client_id: None, text: preview.to_string(), text_elements: Vec::new(), }] @@ -748,6 +750,7 @@ async fn thread_fork_ephemeral_remains_pathless_and_omits_listing() -> Result<() .send_turn_start_request(TurnStartParams { thread_id: fork_thread_id, input: vec![UserInput::Text { + client_id: None, text: "continue".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/thread_inject_items.rs b/codex-rs/app-server/tests/suite/v2/thread_inject_items.rs index 5a45e81e1d5..0965250fe5d 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_inject_items.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_inject_items.rs @@ -93,6 +93,7 @@ async fn thread_inject_items_adds_raw_response_items_to_thread_history() -> Resu .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], @@ -171,6 +172,7 @@ async fn thread_inject_items_adds_raw_response_items_after_a_turn() -> Result<() .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "First turn".to_string(), text_elements: Vec::new(), }], @@ -216,6 +218,7 @@ async fn thread_inject_items_adds_raw_response_items_after_a_turn() -> Result<() .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "Second turn".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/thread_list.rs b/codex-rs/app-server/tests/suite/v2/thread_list.rs index e064ff6e254..74ae440a370 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_list.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_list.rs @@ -228,6 +228,7 @@ async fn thread_list_reports_system_error_idle_flag_after_failed_turn() -> Resul .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![UserInput::Text { + client_id: None, text: "seed history".to_string(), text_elements: Vec::new(), }], @@ -250,6 +251,7 @@ async fn thread_list_reports_system_error_idle_flag_after_failed_turn() -> Resul .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![UserInput::Text { + client_id: None, text: "fail turn".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/thread_read.rs b/codex-rs/app-server/tests/suite/v2/thread_read.rs index 5ba9fa9d2df..2fae6a7b456 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_read.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_read.rs @@ -185,6 +185,7 @@ async fn thread_read_can_include_turns() -> Result<()> { assert_eq!( content, &vec![UserInput::Text { + client_id: None, text: preview.to_string(), text_elements: text_elements.clone().into_iter().map(Into::into).collect(), }] @@ -1199,6 +1200,7 @@ async fn thread_read_reports_system_error_idle_flag_after_failed_turn() -> Resul .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![UserInput::Text { + client_id: None, text: "fail this turn".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/thread_resume.rs b/codex-rs/app-server/tests/suite/v2/thread_resume.rs index 2b85427247f..b8539056fd0 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_resume.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_resume.rs @@ -212,6 +212,7 @@ async fn thread_resume_with_empty_path_uses_running_thread_id() -> Result<()> { .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![UserInput::Text { + client_id: None, text: "materialize rollout".to_string(), text_elements: Vec::new(), }], @@ -280,6 +281,7 @@ async fn turn_start_updates_runtime_workspace_roots_for_loaded_thread() -> Resul .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![UserInput::Text { + client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], @@ -517,6 +519,7 @@ async fn thread_resume_returns_rollout_history() -> Result<()> { assert_eq!( content, &vec![UserInput::Text { + client_id: None, text: preview.to_string(), text_elements: text_elements.clone().into_iter().map(Into::into).collect(), }] @@ -810,6 +813,7 @@ async fn thread_resume_keeps_paused_goal_paused() -> Result<()> { .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![UserInput::Text { + client_id: None, text: "materialize this thread".to_string(), text_elements: Vec::new(), }], @@ -914,6 +918,7 @@ async fn thread_goal_set_preserves_budget_limited_same_objective() -> Result<()> .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![UserInput::Text { + client_id: None, text: "materialize this thread".to_string(), text_elements: Vec::new(), }], @@ -1012,6 +1017,7 @@ async fn thread_goal_set_persists_resumable_stopped_statuses() -> Result<()> { .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![UserInput::Text { + client_id: None, text: "materialize this thread".to_string(), text_elements: Vec::new(), }], @@ -1207,6 +1213,7 @@ async fn thread_goal_clear_deletes_goal_and_notifies() -> Result<()> { .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![UserInput::Text { + client_id: None, text: "materialize this thread".to_string(), text_elements: Vec::new(), }], @@ -2026,6 +2033,7 @@ async fn thread_resume_defers_updated_at_until_turn_start() -> Result<()> { .send_turn_start_request(TurnStartParams { thread_id, input: vec![UserInput::Text { + client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], @@ -2075,6 +2083,7 @@ async fn thread_resume_keeps_in_flight_turn_streaming() -> Result<()> { .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![UserInput::Text { + client_id: None, text: "seed history".to_string(), text_elements: Vec::new(), }], @@ -2100,6 +2109,7 @@ async fn thread_resume_keeps_in_flight_turn_streaming() -> Result<()> { .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![UserInput::Text { + client_id: None, text: "respond with docs".to_string(), text_elements: Vec::new(), }], @@ -2182,6 +2192,7 @@ async fn thread_resume_rejects_history_when_thread_is_running() -> Result<()> { .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![UserInput::Text { + client_id: None, text: "seed history".to_string(), text_elements: Vec::new(), }], @@ -2205,6 +2216,7 @@ async fn thread_resume_rejects_history_when_thread_is_running() -> Result<()> { .send_turn_start_request(TurnStartParams { thread_id: thread_id.clone(), input: vec![UserInput::Text { + client_id: None, text: "keep running".to_string(), text_elements: Vec::new(), }], @@ -2298,6 +2310,7 @@ async fn thread_resume_rejects_mismatched_path_for_running_thread_id() -> Result .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![UserInput::Text { + client_id: None, text: "seed history".to_string(), text_elements: Vec::new(), }], @@ -2321,6 +2334,7 @@ async fn thread_resume_rejects_mismatched_path_for_running_thread_id() -> Result .send_turn_start_request(TurnStartParams { thread_id: thread_id.clone(), input: vec![UserInput::Text { + client_id: None, text: "keep running".to_string(), text_elements: Vec::new(), }], @@ -2434,6 +2448,7 @@ async fn thread_resume_rejoins_running_thread_even_with_override_mismatch() -> R .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![UserInput::Text { + client_id: None, text: "seed history".to_string(), text_elements: Vec::new(), }], @@ -2456,6 +2471,7 @@ async fn thread_resume_rejoins_running_thread_even_with_override_mismatch() -> R .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![UserInput::Text { + client_id: None, text: "keep running".to_string(), text_elements: Vec::new(), }], @@ -2563,6 +2579,7 @@ async fn thread_resume_can_skip_turns_when_thread_is_running() -> Result<()> { .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![UserInput::Text { + client_id: None, text: "seed history".to_string(), text_elements: Vec::new(), }], @@ -2646,6 +2663,7 @@ async fn thread_resume_replays_pending_command_execution_request_approval() -> R .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![UserInput::Text { + client_id: None, text: "seed history".to_string(), text_elements: Vec::new(), }], @@ -2668,6 +2686,7 @@ async fn thread_resume_replays_pending_command_execution_request_approval() -> R .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![UserInput::Text { + client_id: None, text: "run command".to_string(), text_elements: Vec::new(), }], @@ -2784,6 +2803,7 @@ async fn thread_resume_replays_pending_file_change_request_approval() -> Result< .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![UserInput::Text { + client_id: None, text: "seed history".to_string(), text_elements: Vec::new(), }], @@ -2807,6 +2827,7 @@ async fn thread_resume_replays_pending_file_change_request_approval() -> Result< .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![UserInput::Text { + client_id: None, text: "apply patch".to_string(), text_elements: Vec::new(), }], @@ -2951,6 +2972,7 @@ async fn thread_resume_with_overrides_defers_updated_at_until_turn_start() -> Re .send_turn_start_request(TurnStartParams { thread_id: resumed_thread.id, input: vec![UserInput::Text { + client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], @@ -3263,6 +3285,7 @@ async fn start_materialized_thread_and_restart( .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![UserInput::Text { + client_id: None, text: seed_text.to_string(), text_elements: Vec::new(), }], @@ -3352,6 +3375,7 @@ async fn thread_resume_accepts_personality_override() -> Result<()> { .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![UserInput::Text { + client_id: None, text: "seed history".to_string(), text_elements: Vec::new(), }], @@ -3392,6 +3416,7 @@ async fn thread_resume_accepts_personality_override() -> Result<()> { .send_turn_start_request(TurnStartParams { thread_id: resume.thread.id, input: vec![UserInput::Text { + client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/thread_rollback.rs b/codex-rs/app-server/tests/suite/v2/thread_rollback.rs index 5f79db0e265..e5807b5aed6 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_rollback.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_rollback.rs @@ -58,6 +58,7 @@ async fn thread_rollback_drops_last_turns_and_persists_to_rollout() -> Result<() .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: first_text.to_string(), text_elements: Vec::new(), }], @@ -79,6 +80,7 @@ async fn thread_rollback_drops_last_turns_and_persists_to_rollout() -> Result<() .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "Second".to_string(), text_elements: Vec::new(), }], @@ -138,6 +140,7 @@ async fn thread_rollback_drops_last_turns_and_persists_to_rollout() -> Result<() assert_eq!( content, &vec![V2UserInput::Text { + client_id: None, text: first_text.to_string(), text_elements: Vec::new(), }] @@ -168,6 +171,7 @@ async fn thread_rollback_drops_last_turns_and_persists_to_rollout() -> Result<() assert_eq!( content, &vec![V2UserInput::Text { + client_id: None, text: first_text.to_string(), text_elements: Vec::new(), }] diff --git a/codex-rs/app-server/tests/suite/v2/thread_settings_update.rs b/codex-rs/app-server/tests/suite/v2/thread_settings_update.rs index aef2e961c08..7b5069f6dad 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_settings_update.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_settings_update.rs @@ -260,6 +260,7 @@ async fn turn_start_settings_override_emits_thread_settings_updated() -> Result< .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "hello".to_string(), text_elements: Vec::new(), }], @@ -306,6 +307,7 @@ async fn start_text_turn(mcp: &mut McpProcess, thread_id: String) -> Result<()> .send_turn_start_request(TurnStartParams { thread_id, input: vec![V2UserInput::Text { + client_id: None, text: "hello".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/thread_shell_command.rs b/codex-rs/app-server/tests/suite/v2/thread_shell_command.rs index 4ab677aca14..19f52179d9d 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_shell_command.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_shell_command.rs @@ -276,6 +276,7 @@ async fn thread_shell_command_uses_existing_active_turn() -> Result<()> { .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "run python".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/thread_status.rs b/codex-rs/app-server/tests/suite/v2/thread_status.rs index 957969c3eaf..3d75f021e86 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_status.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_status.rs @@ -49,6 +49,7 @@ async fn thread_status_changed_emits_runtime_updates() -> Result<()> { .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "collect status updates".to_string(), text_elements: Vec::new(), }], @@ -172,6 +173,7 @@ async fn thread_status_changed_can_be_opted_out() -> Result<()> { .send_turn_start_request(TurnStartParams { thread_id: thread.id, input: vec![V2UserInput::Text { + client_id: None, text: "run once".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/thread_unarchive.rs b/codex-rs/app-server/tests/suite/v2/thread_unarchive.rs index d2f8f268a2d..70eec5db97f 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_unarchive.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_unarchive.rs @@ -82,6 +82,7 @@ async fn thread_unarchive_moves_rollout_back_into_sessions_directory() -> Result .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![UserInput::Text { + client_id: None, text: "materialize".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/thread_unsubscribe.rs b/codex-rs/app-server/tests/suite/v2/thread_unsubscribe.rs index 45437b7ee11..237aa03c032 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_unsubscribe.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_unsubscribe.rs @@ -152,6 +152,7 @@ async fn thread_unsubscribe_during_turn_keeps_turn_running() -> Result<()> { .send_turn_start_request(TurnStartParams { thread_id: thread_id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "run deterministic tool".to_string(), text_elements: Vec::new(), }], @@ -261,6 +262,7 @@ async fn thread_unsubscribe_preserves_cached_status_before_idle_unload() -> Resu .send_turn_start_request(TurnStartParams { thread_id: thread_id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "fail this turn".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/turn_interrupt.rs b/codex-rs/app-server/tests/suite/v2/turn_interrupt.rs index aedc54e0168..b4cb25fbfbb 100644 --- a/codex-rs/app-server/tests/suite/v2/turn_interrupt.rs +++ b/codex-rs/app-server/tests/suite/v2/turn_interrupt.rs @@ -79,6 +79,7 @@ async fn turn_interrupt_aborts_running_turn() -> Result<()> { .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "run sleep".to_string(), text_elements: Vec::new(), }], @@ -160,6 +161,7 @@ async fn turn_interrupt_rejects_completed_turn() -> Result<()> { .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "say done".to_string(), text_elements: Vec::new(), }], @@ -254,6 +256,7 @@ async fn turn_interrupt_resolves_pending_command_approval_request() -> Result<() .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "run python".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/turn_start.rs b/codex-rs/app-server/tests/suite/v2/turn_start.rs index d0f1c13246c..05b66177be5 100644 --- a/codex-rs/app-server/tests/suite/v2/turn_start.rs +++ b/codex-rs/app-server/tests/suite/v2/turn_start.rs @@ -142,6 +142,7 @@ async fn run_local_image_turn(detail: Option) -> Result> .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![V2UserInput::LocalImage { + client_id: None, path: image_path, detail, }], @@ -336,6 +337,7 @@ async fn turn_start_additional_context_flows_to_model_input() -> Result<()> { .send_turn_start_request(TurnStartParams { thread_id: thread.id, input: vec![V2UserInput::Text { + client_id: None, text: "inspect tab".to_string(), text_elements: Vec::new(), }], @@ -421,6 +423,7 @@ async fn turn_start_sends_originator_header() -> Result<()> { .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], @@ -493,6 +496,7 @@ async fn turn_start_emits_user_message_item_with_text_elements() -> Result<()> { .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "Hello".to_string(), text_elements: text_elements.clone(), }], @@ -525,6 +529,7 @@ async fn turn_start_emits_user_message_item_with_text_elements() -> Result<()> { assert_eq!( content, vec![V2UserInput::Text { + client_id: None, text: "Hello".to_string(), text_elements, }] @@ -596,6 +601,7 @@ async fn turn_start_emits_thread_scoped_warning_notification_for_trimmed_skills( .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], @@ -692,6 +698,7 @@ async fn turn_start_sends_service_tier_id_to_model_request() -> Result<()> { thread_id: thread.id, service_tier: Some(Some(service_tier_id.clone())), input: vec![V2UserInput::Text { + client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], @@ -762,6 +769,7 @@ async fn thread_start_omits_empty_instruction_overrides_from_model_request() -> .send_turn_start_request(TurnStartParams { thread_id: thread.id, input: vec![V2UserInput::Text { + client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], @@ -841,6 +849,7 @@ async fn turn_start_tracks_turn_event_analytics() -> Result<()> { .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Image { + client_id: None, url: "https://example.com/a.png".to_string(), detail: None, }], @@ -930,10 +939,12 @@ async fn turn_start_accepts_text_at_limit_with_mention_item() -> Result<()> { thread_id: thread.id, input: vec![ V2UserInput::Text { + client_id: None, text: "x".repeat(MAX_USER_INPUT_TEXT_CHARS), text_elements: Vec::new(), }, V2UserInput::Mention { + client_id: None, name: "Demo App".to_string(), path: "app://demo-app".to_string(), }, @@ -993,10 +1004,12 @@ async fn turn_start_rejects_combined_oversized_text_input() -> Result<()> { thread_id: thread.id, input: vec![ V2UserInput::Text { + client_id: None, text: first, text_elements: Vec::new(), }, V2UserInput::Text { + client_id: None, text: second, text_elements: Vec::new(), }, @@ -1066,6 +1079,7 @@ async fn turn_start_rejects_invalid_permission_selection_before_starting_turn() .send_turn_start_request(TurnStartParams { thread_id: thread.id, input: vec![V2UserInput::Text { + client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], @@ -1138,6 +1152,7 @@ async fn turn_start_rejects_unknown_environment_before_starting_turn() -> Result .send_turn_start_request(TurnStartParams { thread_id: thread.id, input: vec![V2UserInput::Text { + client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], @@ -1211,6 +1226,7 @@ async fn turn_start_emits_notifications_and_accepts_model_override() -> Result<( .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], @@ -1263,6 +1279,7 @@ async fn turn_start_emits_notifications_and_accepts_model_override() -> Result<( .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "Second".to_string(), text_elements: Vec::new(), }], @@ -1361,6 +1378,7 @@ async fn turn_start_accepts_collaboration_mode_override_v2() -> Result<()> { .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], @@ -1450,6 +1468,7 @@ async fn turn_start_uses_thread_feature_overrides_for_request_user_input_tool_de .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], @@ -1521,6 +1540,7 @@ async fn turn_start_accepts_personality_override_v2() -> Result<()> { .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], @@ -1602,6 +1622,7 @@ async fn turn_start_change_personality_mid_thread_v2() -> Result<()> { .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], @@ -1626,6 +1647,7 @@ async fn turn_start_change_personality_mid_thread_v2() -> Result<()> { .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "Hello again".to_string(), text_elements: Vec::new(), }], @@ -1728,6 +1750,7 @@ async fn turn_start_uses_migrated_pragmatic_personality_without_override_v2() -> .send_turn_start_request(TurnStartParams { thread_id: thread.id, input: vec![V2UserInput::Text { + client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], @@ -1848,6 +1871,7 @@ async fn turn_start_exec_approval_toggle_v2() -> Result<()> { .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "run python".to_string(), text_elements: Vec::new(), }], @@ -1912,6 +1936,7 @@ async fn turn_start_exec_approval_toggle_v2() -> Result<()> { .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "run python again".to_string(), text_elements: Vec::new(), }], @@ -1989,6 +2014,7 @@ async fn turn_start_exec_approval_decline_v2() -> Result<()> { .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "run python".to_string(), text_elements: Vec::new(), }], @@ -2144,6 +2170,7 @@ async fn turn_start_updates_sandbox_and_cwd_between_turns_v2() -> Result<()> { environments: None, thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "first turn".to_string(), text_elements: Vec::new(), }], @@ -2187,6 +2214,7 @@ async fn turn_start_updates_sandbox_and_cwd_between_turns_v2() -> Result<()> { environments: None, thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "second turn".to_string(), text_elements: Vec::new(), }], @@ -2329,6 +2357,7 @@ stream_max_retries = 0 .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "select dev profile".to_string(), text_elements: Vec::new(), }], @@ -2352,6 +2381,7 @@ stream_max_retries = 0 .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "write in new root".to_string(), text_elements: Vec::new(), }], @@ -2484,6 +2514,7 @@ async fn run_environment_selection_case( .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: format!("run {}", case.name), text_elements: Vec::new(), }], @@ -2599,6 +2630,7 @@ async fn turn_start_file_change_approval_v2() -> Result<()> { .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "apply patch".into(), text_elements: Vec::new(), }], @@ -2789,6 +2821,7 @@ async fn turn_start_does_not_stream_apply_patch_change_updates_without_feature_v .send_turn_start_request(TurnStartParams { thread_id: thread.id, input: vec![V2UserInput::Text { + client_id: None, text: "apply patch".into(), text_elements: Vec::new(), }], @@ -2926,6 +2959,7 @@ async fn turn_start_streams_apply_patch_change_updates_v2() -> Result<()> { .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "apply patch".into(), text_elements: Vec::new(), }], @@ -3056,6 +3090,7 @@ async fn turn_start_emits_spawn_agent_item_with_model_metadata_v2() -> Result<() .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: PARENT_PROMPT.to_string(), text_elements: Vec::new(), }], @@ -3275,6 +3310,7 @@ config_file = "./custom-role.toml" .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: PARENT_PROMPT.to_string(), text_elements: Vec::new(), }], @@ -3420,6 +3456,7 @@ async fn turn_start_file_change_approval_accept_for_session_persists_v2() -> Res .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "apply patch 1".into(), text_elements: Vec::new(), }], @@ -3492,6 +3529,7 @@ async fn turn_start_file_change_approval_accept_for_session_persists_v2() -> Res .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "apply patch 2".into(), text_elements: Vec::new(), }], @@ -3590,6 +3628,7 @@ async fn turn_start_file_change_approval_decline_v2() -> Result<()> { .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "apply patch".into(), text_elements: Vec::new(), }], @@ -3735,6 +3774,7 @@ async fn command_execution_notifications_include_process_id() -> Result<()> { .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "run a command".to_string(), text_elements: Vec::new(), }], @@ -3870,6 +3910,7 @@ async fn turn_start_with_elevated_override_does_not_persist_project_trust() -> R cwd: Some(workspace.path().to_path_buf()), sandbox_policy: Some(codex_app_server_protocol::SandboxPolicy::DangerFullAccess), input: vec![V2UserInput::Text { + client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/turn_start_zsh_fork.rs b/codex-rs/app-server/tests/suite/v2/turn_start_zsh_fork.rs index 7b7a6c25632..f67e683ca32 100644 --- a/codex-rs/app-server/tests/suite/v2/turn_start_zsh_fork.rs +++ b/codex-rs/app-server/tests/suite/v2/turn_start_zsh_fork.rs @@ -118,6 +118,7 @@ async fn turn_start_shell_zsh_fork_executes_command_v2() -> Result<()> { .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "run echo hi".to_string(), text_elements: Vec::new(), }], @@ -236,6 +237,7 @@ async fn turn_start_shell_zsh_fork_exec_approval_decline_v2() -> Result<()> { .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "run python".to_string(), text_elements: Vec::new(), }], @@ -368,6 +370,7 @@ async fn turn_start_shell_zsh_fork_exec_approval_cancel_v2() -> Result<()> { .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "run python".to_string(), text_elements: Vec::new(), }], @@ -526,6 +529,7 @@ async fn turn_start_shell_zsh_fork_subcommand_decline_marks_parent_declined_v2() .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "remove both files".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/turn_steer.rs b/codex-rs/app-server/tests/suite/v2/turn_steer.rs index 0c4dcabe775..770e0a24953 100644 --- a/codex-rs/app-server/tests/suite/v2/turn_steer.rs +++ b/codex-rs/app-server/tests/suite/v2/turn_steer.rs @@ -68,6 +68,7 @@ async fn turn_steer_requires_active_turn() -> Result<()> { .send_turn_steer_request(TurnSteerParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "steer".to_string(), text_elements: Vec::new(), }], @@ -153,6 +154,7 @@ async fn turn_steer_rejects_oversized_text_input() -> Result<()> { .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "run sleep".to_string(), text_elements: Vec::new(), }], @@ -178,6 +180,7 @@ async fn turn_steer_rejects_oversized_text_input() -> Result<()> { .send_turn_steer_request(TurnSteerParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: oversized_input.clone(), text_elements: Vec::new(), }], @@ -263,6 +266,7 @@ async fn turn_steer_returns_active_turn_id() -> Result<()> { .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "run sleep".to_string(), text_elements: Vec::new(), }], @@ -287,6 +291,7 @@ async fn turn_steer_returns_active_turn_id() -> Result<()> { .send_turn_steer_request(TurnSteerParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "steer".to_string(), text_elements: Vec::new(), }], @@ -367,6 +372,7 @@ async fn turn_steer_rejects_context_only_input_without_merging_context() -> Resu .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), input: vec![V2UserInput::Text { + client_id: None, text: "run sleep".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/web_search.rs b/codex-rs/app-server/tests/suite/v2/web_search.rs index 927f14ca9d2..8a1ef612883 100644 --- a/codex-rs/app-server/tests/suite/v2/web_search.rs +++ b/codex-rs/app-server/tests/suite/v2/web_search.rs @@ -89,6 +89,7 @@ async fn standalone_web_search_round_trips_encrypted_output() -> Result<()> { .send_turn_start_request(TurnStartParams { thread_id: thread.id, input: vec![V2UserInput::Text { + client_id: None, text: "Search the web".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/cli/src/main.rs b/codex-rs/cli/src/main.rs index e0812e3fdc9..aaa0f508f8f 100644 --- a/codex-rs/cli/src/main.rs +++ b/codex-rs/cli/src/main.rs @@ -1742,10 +1742,15 @@ async fn run_debug_prompt_input_command( .images .into_iter() .chain(cmd.images) - .map(|path| UserInput::LocalImage { path, detail: None }) + .map(|path| UserInput::LocalImage { + client_id: None, + path, + detail: None, + }) .collect::>(); if let Some(prompt) = cmd.prompt.or(interactive.prompt) { input.push(UserInput::Text { + client_id: None, text: prompt.replace("\r\n", "\n").replace('\r', "\n"), text_elements: Vec::new(), }); diff --git a/codex-rs/core-skills/src/injection.rs b/codex-rs/core-skills/src/injection.rs index df62f42e85a..45979fa5c49 100644 --- a/codex-rs/core-skills/src/injection.rs +++ b/codex-rs/core-skills/src/injection.rs @@ -132,7 +132,7 @@ pub fn collect_explicit_skill_mentions( let mut blocked_plain_names: HashSet = HashSet::new(); for input in inputs { - if let UserInput::Skill { name, path } = input { + if let UserInput::Skill { name, path, .. } = input { blocked_plain_names.insert(name.clone()); let Ok(path) = AbsolutePathBuf::relative_to_current_dir(path) else { continue; diff --git a/codex-rs/core-skills/src/injection_tests.rs b/codex-rs/core-skills/src/injection_tests.rs index 78aa1958952..5ff8f38f2b3 100644 --- a/codex-rs/core-skills/src/injection_tests.rs +++ b/codex-rs/core-skills/src/injection_tests.rs @@ -136,6 +136,7 @@ fn collect_explicit_skill_mentions_text_respects_skill_order() { let beta = make_skill("beta-skill", "/tmp/beta"); let skills = vec![beta.clone(), alpha.clone()]; let inputs = vec![UserInput::Text { + client_id: None, text: "first $alpha-skill then $beta-skill".to_string(), text_elements: Vec::new(), }]; @@ -154,10 +155,12 @@ fn collect_explicit_skill_mentions_prioritizes_structured_inputs() { let skills = vec![alpha.clone(), beta.clone()]; let inputs = vec![ UserInput::Text { + client_id: None, text: "please run $alpha-skill".to_string(), text_elements: Vec::new(), }, UserInput::Skill { + client_id: None, name: "beta-skill".to_string(), path: test_path_buf("/tmp/beta"), }, @@ -175,10 +178,12 @@ fn collect_explicit_skill_mentions_skips_invalid_structured_and_blocks_plain_fal let skills = vec![alpha]; let inputs = vec![ UserInput::Text { + client_id: None, text: "please run $alpha-skill".to_string(), text_elements: Vec::new(), }, UserInput::Skill { + client_id: None, name: "alpha-skill".to_string(), path: test_path_buf("/tmp/missing"), }, @@ -196,10 +201,12 @@ fn collect_explicit_skill_mentions_skips_disabled_structured_and_blocks_plain_fa let skills = vec![alpha]; let inputs = vec![ UserInput::Text { + client_id: None, text: "please run $alpha-skill".to_string(), text_elements: Vec::new(), }, UserInput::Skill { + client_id: None, name: "alpha-skill".to_string(), path: test_path_buf("/tmp/alpha"), }, @@ -218,6 +225,7 @@ fn collect_explicit_skill_mentions_dedupes_by_path() { let skills = vec![alpha.clone()]; let mention = linked_skill_mention("alpha-skill", "/tmp/alpha"); let inputs = vec![UserInput::Text { + client_id: None, text: format!("use {mention} and {mention}"), text_elements: Vec::new(), }]; @@ -234,6 +242,7 @@ fn collect_explicit_skill_mentions_skips_ambiguous_name() { let beta = make_skill("demo-skill", "/tmp/beta"); let skills = vec![alpha, beta]; let inputs = vec![UserInput::Text { + client_id: None, text: "use $demo-skill and again $demo-skill".to_string(), text_elements: Vec::new(), }]; @@ -250,6 +259,7 @@ fn collect_explicit_skill_mentions_prefers_linked_path_over_name() { let beta = make_skill("demo-skill", "/tmp/beta"); let skills = vec![alpha, beta.clone()]; let inputs = vec![UserInput::Text { + client_id: None, text: format!( "use $demo-skill and {}", linked_skill_mention("demo-skill", "/tmp/beta") @@ -268,6 +278,7 @@ fn collect_explicit_skill_mentions_skips_plain_name_when_connector_matches() { let alpha = make_skill("alpha-skill", "/tmp/alpha"); let skills = vec![alpha]; let inputs = vec![UserInput::Text { + client_id: None, text: "use $alpha-skill".to_string(), text_elements: Vec::new(), }]; @@ -283,6 +294,7 @@ fn collect_explicit_skill_mentions_allows_explicit_path_with_connector_conflict( let alpha = make_skill("alpha-skill", "/tmp/alpha"); let skills = vec![alpha.clone()]; let inputs = vec![UserInput::Text { + client_id: None, text: format!("use {}", linked_skill_mention("alpha-skill", "/tmp/alpha")), text_elements: Vec::new(), }]; @@ -299,6 +311,7 @@ fn collect_explicit_skill_mentions_skips_when_linked_path_disabled() { let beta = make_skill("demo-skill", "/tmp/beta"); let skills = vec![alpha, beta]; let inputs = vec![UserInput::Text { + client_id: None, text: format!("use {}", linked_skill_mention("demo-skill", "/tmp/alpha")), text_elements: Vec::new(), }]; @@ -316,6 +329,7 @@ fn collect_explicit_skill_mentions_prefers_resource_path() { let beta = make_skill("demo-skill", "/tmp/beta"); let skills = vec![alpha, beta.clone()]; let inputs = vec![UserInput::Text { + client_id: None, text: format!("use {}", linked_skill_mention("demo-skill", "/tmp/beta")), text_elements: Vec::new(), }]; @@ -332,6 +346,7 @@ fn collect_explicit_skill_mentions_skips_missing_path_with_no_fallback() { let beta = make_skill("demo-skill", "/tmp/beta"); let skills = vec![alpha, beta]; let inputs = vec![UserInput::Text { + client_id: None, text: format!("use {}", linked_skill_mention("demo-skill", "/tmp/missing")), text_elements: Vec::new(), }]; @@ -347,6 +362,7 @@ fn collect_explicit_skill_mentions_skips_missing_path_without_fallback() { let alpha = make_skill("demo-skill", "/tmp/alpha"); let skills = vec![alpha]; let inputs = vec![UserInput::Text { + client_id: None, text: format!("use {}", linked_skill_mention("demo-skill", "/tmp/missing")), text_elements: Vec::new(), }]; diff --git a/codex-rs/core/src/agent/control.rs b/codex-rs/core/src/agent/control.rs index 1091761f79b..7e87bf73d6d 100644 --- a/codex-rs/core/src/agent/control.rs +++ b/codex-rs/core/src/agent/control.rs @@ -1296,8 +1296,10 @@ pub(crate) fn render_input_preview(initial_operation: &Op) -> String { UserInput::LocalImage { path, .. } => { format!("[local_image:{}]", path.display()) } - UserInput::Skill { name, path } => format!("[skill:${name}]({})", path.display()), - UserInput::Mention { name, path } => format!("[mention:${name}]({path})"), + UserInput::Skill { name, path, .. } => { + format!("[skill:${name}]({})", path.display()) + } + UserInput::Mention { name, path, .. } => format!("[mention:${name}]({path})"), _ => "[input]".to_string(), }) .collect::>() diff --git a/codex-rs/core/src/agent/control_tests.rs b/codex-rs/core/src/agent/control_tests.rs index 43a6e47bfdb..582bdc14a46 100644 --- a/codex-rs/core/src/agent/control_tests.rs +++ b/codex-rs/core/src/agent/control_tests.rs @@ -57,6 +57,7 @@ async fn test_config() -> (TempDir, Config) { fn text_input(text: &str) -> Op { vec![UserInput::Text { + client_id: None, text: text.to_string(), text_elements: Vec::new(), }] @@ -247,6 +248,7 @@ async fn send_input_errors_when_manager_dropped() { .send_input( ThreadId::new(), vec![UserInput::Text { + client_id: None, text: "hello".to_string(), text_elements: Vec::new(), }] @@ -359,6 +361,7 @@ async fn send_input_errors_when_thread_missing() { .send_input( thread_id, vec![UserInput::Text { + client_id: None, text: "hello".to_string(), text_elements: Vec::new(), }] @@ -426,6 +429,7 @@ async fn send_input_submits_user_message() { .send_input( thread_id, vec![UserInput::Text { + client_id: None, text: "hello from tests".to_string(), text_elements: Vec::new(), }] @@ -439,6 +443,7 @@ async fn send_input_submits_user_message() { Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello from tests".to_string(), text_elements: Vec::new(), }], @@ -540,6 +545,7 @@ async fn spawn_agent_creates_thread_and_sends_prompt() { Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "spawned".to_string(), text_elements: Vec::new(), }], @@ -713,6 +719,7 @@ async fn spawn_agent_can_fork_parent_thread_history_with_sanitized_items() { Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "child task".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/src/compact.rs b/codex-rs/core/src/compact.rs index fd388b273cf..51c8b44ab92 100644 --- a/codex-rs/core/src/compact.rs +++ b/codex-rs/core/src/compact.rs @@ -76,6 +76,7 @@ pub(crate) async fn run_inline_auto_compact_task( ) -> CodexResult<()> { let prompt = turn_context.compact_prompt().to_string(); let input = vec![UserInput::Text { + client_id: None, text: prompt, // Compaction prompt is synthesized; no UI element ranges to preserve. text_elements: Vec::new(), diff --git a/codex-rs/core/src/event_mapping.rs b/codex-rs/core/src/event_mapping.rs index d3cae7feed4..143941be68b 100644 --- a/codex-rs/core/src/event_mapping.rs +++ b/codex-rs/core/src/event_mapping.rs @@ -85,6 +85,7 @@ fn parse_user_message(message: &[ContentItem]) -> Option { continue; } content.push(UserInput::Text { + client_id: None, text: text.clone(), // Model input content does not carry UI element ranges. text_elements: Vec::new(), @@ -92,6 +93,7 @@ fn parse_user_message(message: &[ContentItem]) -> Option { } ContentItem::InputImage { image_url, detail } => { content.push(UserInput::Image { + client_id: None, image_url: image_url.clone(), detail: *detail, }); diff --git a/codex-rs/core/src/event_mapping_tests.rs b/codex-rs/core/src/event_mapping_tests.rs index 57d6362f55d..0985abd6b56 100644 --- a/codex-rs/core/src/event_mapping_tests.rs +++ b/codex-rs/core/src/event_mapping_tests.rs @@ -45,14 +45,17 @@ fn parses_user_message_with_text_and_two_images() { TurnItem::UserMessage(user) => { let expected_content = vec![ UserInput::Text { + client_id: None, text: "Hello world".to_string(), text_elements: Vec::new(), }, UserInput::Image { + client_id: None, image_url: img1, detail: Some(DEFAULT_IMAGE_DETAIL), }, UserInput::Image { + client_id: None, image_url: img2, detail: Some(DEFAULT_IMAGE_DETAIL), }, @@ -94,10 +97,12 @@ fn skips_local_image_label_text() { TurnItem::UserMessage(user) => { let expected_content = vec![ UserInput::Image { + client_id: None, image_url, detail: Some(DEFAULT_IMAGE_DETAIL), }, UserInput::Text { + client_id: None, text: user_text, text_elements: Vec::new(), }, @@ -175,10 +180,12 @@ fn skips_unnamed_image_label_text() { TurnItem::UserMessage(user) => { let expected_content = vec![ UserInput::Image { + client_id: None, image_url, detail: Some(DEFAULT_IMAGE_DETAIL), }, UserInput::Text { + client_id: None, text: user_text, text_elements: Vec::new(), }, diff --git a/codex-rs/core/src/guardian/prompt.rs b/codex-rs/core/src/guardian/prompt.rs index b1b132a9844..4310cd772da 100644 --- a/codex-rs/core/src/guardian/prompt.rs +++ b/codex-rs/core/src/guardian/prompt.rs @@ -153,6 +153,7 @@ pub(crate) async fn build_guardian_prompt_items( let mut items = Vec::new(); let mut push_text = |text: String| { items.push(UserInput::Text { + client_id: None, text, text_elements: Vec::new(), }); diff --git a/codex-rs/core/src/plugins/mentions_tests.rs b/codex-rs/core/src/plugins/mentions_tests.rs index 37c9adb886b..8596ad24e42 100644 --- a/codex-rs/core/src/plugins/mentions_tests.rs +++ b/codex-rs/core/src/plugins/mentions_tests.rs @@ -9,6 +9,7 @@ use crate::plugins::PluginCapabilitySummary; fn text_input(text: &str) -> UserInput { UserInput::Text { + client_id: None, text: text.to_string(), text_elements: Vec::new(), } @@ -39,6 +40,7 @@ fn collect_explicit_app_ids_dedupes_structured_and_linked_mentions() { let input = vec![ text_input("use [$calendar](app://calendar)"), UserInput::Mention { + client_id: None, name: "calendar".to_string(), path: "app://calendar".to_string(), }, @@ -56,14 +58,17 @@ fn collect_explicit_app_ids_ignores_non_app_paths() { "use [$docs](mcp://docs) and [$skill](skill://team/skill) and [$file](/tmp/file.txt)", ), UserInput::Mention { + client_id: None, name: "docs".to_string(), path: "mcp://docs".to_string(), }, UserInput::Mention { + client_id: None, name: "skill".to_string(), path: "skill://team/skill".to_string(), }, UserInput::Mention { + client_id: None, name: "file".to_string(), path: "/tmp/file.txt".to_string(), }, @@ -83,6 +88,7 @@ fn collect_explicit_plugin_mentions_from_structured_paths() { let mentioned = collect_explicit_plugin_mentions( &[UserInput::Mention { + client_id: None, name: "sample".to_string(), path: "plugin://sample@test".to_string(), }], @@ -118,6 +124,7 @@ fn collect_explicit_plugin_mentions_dedupes_structured_and_linked_mentions() { &[ text_input("use [@sample](plugin://sample@test)"), UserInput::Mention { + client_id: None, name: "sample".to_string(), path: "plugin://sample@test".to_string(), }, diff --git a/codex-rs/core/src/session/mod.rs b/codex-rs/core/src/session/mod.rs index 63316356646..d79f90adba0 100644 --- a/codex-rs/core/src/session/mod.rs +++ b/codex-rs/core/src/session/mod.rs @@ -1065,6 +1065,7 @@ impl Session { Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text, text_elements: Vec::new(), }], diff --git a/codex-rs/core/src/session/review.rs b/codex-rs/core/src/session/review.rs index 762ebc14580..58057fd173e 100644 --- a/codex-rs/core/src/session/review.rs +++ b/codex-rs/core/src/session/review.rs @@ -138,6 +138,7 @@ pub(super) async fn spawn_review_thread( // Seed the child task with the review prompt as the initial user message. let input = vec![TurnInput::UserInput(vec![UserInput::Text { + client_id: None, text: review_prompt, // Review prompt is synthesized; no UI element ranges to preserve. text_elements: Vec::new(), diff --git a/codex-rs/core/src/session/tests.rs b/codex-rs/core/src/session/tests.rs index 7cca56dfb24..14bf1f6088f 100644 --- a/codex-rs/core/src/session/tests.rs +++ b/codex-rs/core/src/session/tests.rs @@ -2367,6 +2367,7 @@ async fn fork_startup_context_then_first_turn_diff_snapshot() -> anyhow::Result< .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "fork seed".into(), text_elements: Vec::new(), }], @@ -2414,6 +2415,7 @@ async fn fork_startup_context_then_first_turn_diff_snapshot() -> anyhow::Result< .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "after fork".into(), text_elements: Vec::new(), }], @@ -5549,6 +5551,7 @@ async fn user_turn_updates_approvals_reviewer() { "sub-1".to_string(), Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: "hello".to_string(), text_elements: Vec::new(), }], @@ -5878,6 +5881,7 @@ async fn spawn_task_turn_span_inherits_dispatch_trace_context() { sess.spawn_task( Arc::clone(&tc), vec![TurnInput::UserInput(vec![UserInput::Text { + client_id: None, text: "hello".to_string(), text_elements: Vec::new(), }])], @@ -6690,6 +6694,7 @@ async fn spawn_task_does_not_update_previous_turn_settings_for_non_run_turn_task sess.set_previous_turn_settings(/*previous_turn_settings*/ None) .await; let input = vec![TurnInput::UserInput(vec![UserInput::Text { + client_id: None, text: "hello".to_string(), text_elements: Vec::new(), }])]; @@ -7959,6 +7964,7 @@ impl SessionTask for GuardianDeniedApprovalTask { async fn guardian_auto_review_interrupts_after_three_consecutive_denials() { let (sess, tc, rx) = make_session_and_context_with_rx().await; let input = vec![TurnInput::UserInput(vec![UserInput::Text { + client_id: None, text: "trigger guardian denials".to_string(), text_elements: Vec::new(), }])]; @@ -7990,6 +7996,7 @@ async fn guardian_auto_review_interrupts_after_three_consecutive_denials() { async fn guardian_helper_review_interrupts_after_three_consecutive_denials() { let (sess, tc, rx) = make_session_and_context_with_rx().await; let input = vec![TurnInput::UserInput(vec![UserInput::Text { + client_id: None, text: "keep turn active for helper reviews".to_string(), text_elements: Vec::new(), }])]; @@ -8050,6 +8057,7 @@ async fn guardian_helper_review_interrupts_after_three_consecutive_denials() { async fn abort_regular_task_emits_marker_before_turn_aborted() { let (sess, tc, rx) = make_session_and_context_with_rx().await; let input = vec![TurnInput::UserInput(vec![UserInput::Text { + client_id: None, text: "hello".to_string(), text_elements: Vec::new(), }])]; @@ -8088,6 +8096,7 @@ async fn abort_regular_task_emits_marker_before_turn_aborted() { async fn abort_gracefully_emits_marker_before_turn_aborted() { let (sess, tc, rx) = make_session_and_context_with_rx().await; let input = vec![TurnInput::UserInput(vec![UserInput::Text { + client_id: None, text: "hello".to_string(), text_elements: Vec::new(), }])]; @@ -8126,6 +8135,7 @@ async fn abort_gracefully_emits_marker_before_turn_aborted() { async fn task_finish_emits_turn_item_lifecycle_for_leftover_pending_user_input() { let (sess, tc, rx) = make_session_and_context_with_rx().await; let input = vec![TurnInput::UserInput(vec![UserInput::Text { + client_id: None, text: "hello".to_string(), text_elements: Vec::new(), }])]; @@ -8146,6 +8156,7 @@ async fn task_finish_emits_turn_item_lifecycle_for_leftover_pending_user_input() Some("pending marker".to_string()), ); let pending_user_input = vec![UserInput::Text { + client_id: None, text: "late pending input".to_string(), text_elements: vec![text_element.clone()], }]; @@ -8242,6 +8253,7 @@ async fn task_finish_emits_turn_item_lifecycle_for_leftover_pending_user_input() async fn steer_input_requires_active_turn() { let (sess, _tc, _rx) = make_session_and_context_with_rx().await; let input = vec![UserInput::Text { + client_id: None, text: "steer".to_string(), text_elements: Vec::new(), }]; @@ -8263,6 +8275,7 @@ async fn steer_input_requires_active_turn() { async fn steer_input_enforces_expected_turn_id() { let (sess, tc, _rx) = make_session_and_context_with_rx().await; let input = vec![TurnInput::UserInput(vec![UserInput::Text { + client_id: None, text: "hello".to_string(), text_elements: Vec::new(), }])]; @@ -8277,6 +8290,7 @@ async fn steer_input_enforces_expected_turn_id() { .await; let steer_input = vec![UserInput::Text { + client_id: None, text: "steer".to_string(), text_elements: Vec::new(), }]; @@ -8309,6 +8323,7 @@ async fn steer_input_rejects_non_regular_turns() { ] { let (sess, _tc, _rx) = make_session_and_context_with_rx().await; let input = vec![TurnInput::UserInput(vec![UserInput::Text { + client_id: None, text: "hello".to_string(), text_elements: Vec::new(), }])]; @@ -8324,6 +8339,7 @@ async fn steer_input_rejects_non_regular_turns() { .await; let steer_input = vec![UserInput::Text { + client_id: None, text: "steer".to_string(), text_elements: Vec::new(), }]; @@ -8347,6 +8363,7 @@ async fn steer_input_rejects_non_regular_turns() { async fn steer_input_returns_active_turn_id() { let (sess, tc, _rx) = make_session_and_context_with_rx().await; let input = vec![TurnInput::UserInput(vec![UserInput::Text { + client_id: None, text: "hello".to_string(), text_elements: Vec::new(), }])]; @@ -8361,6 +8378,7 @@ async fn steer_input_returns_active_turn_id() { .await; let steer_input = vec![UserInput::Text { + client_id: None, text: "steer".to_string(), text_elements: Vec::new(), }]; @@ -8532,6 +8550,7 @@ async fn active_goal_continuation_runs_again_after_no_tool_turn() -> anyhow::Res .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "write a benchmark note".into(), text_elements: Vec::new(), }], @@ -8638,6 +8657,7 @@ async fn pending_request_user_input_does_not_spawn_extra_goal_continuation() -> .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "write a benchmark note".into(), text_elements: Vec::new(), }], @@ -9186,6 +9206,7 @@ async fn completed_goal_accounts_current_turn_tokens_before_tool_response() -> a .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "write a report".into(), text_elements: Vec::new(), }], @@ -9355,6 +9376,7 @@ async fn steered_input_reopens_mailbox_delivery_for_current_turn() { .await; sess.steer_input( vec![UserInput::Text { + client_id: None, text: "follow up".to_string(), text_elements: Vec::new(), }], @@ -9369,6 +9391,7 @@ async fn steered_input_reopens_mailbox_delivery_for_current_turn() { sess.input_queue.get_pending_input(&sess.active_turn).await, vec![ TurnInput::UserInput(vec![UserInput::Text { + client_id: None, text: "follow up".to_string(), text_elements: Vec::new(), }]), @@ -9405,6 +9428,7 @@ async fn stale_defer_mailbox_delivery_does_not_override_steered_input() { .await; sess.steer_input( vec![UserInput::Text { + client_id: None, text: "follow up".to_string(), text_elements: Vec::new(), }], @@ -9423,6 +9447,7 @@ async fn stale_defer_mailbox_delivery_does_not_override_steered_input() { sess.input_queue.get_pending_input(&sess.active_turn).await, vec![ TurnInput::UserInput(vec![UserInput::Text { + client_id: None, text: "follow up".to_string(), text_elements: Vec::new(), }]), @@ -9491,6 +9516,7 @@ async fn tool_calls_reopen_mailbox_delivery_for_current_turn() { async fn abort_review_task_emits_exited_then_aborted_and_records_history() { let (sess, tc, rx) = make_session_and_context_with_rx().await; let input = vec![TurnInput::UserInput(vec![UserInput::Text { + client_id: None, text: "start review".to_string(), text_elements: Vec::new(), }])]; diff --git a/codex-rs/core/src/tasks/compact.rs b/codex-rs/core/src/tasks/compact.rs index adc98e413a8..03a23e5c774 100644 --- a/codex-rs/core/src/tasks/compact.rs +++ b/codex-rs/core/src/tasks/compact.rs @@ -55,6 +55,7 @@ impl SessionTask for CompactTask { /*manual*/ true, ); let input = vec![UserInput::Text { + client_id: None, text: ctx.compact_prompt().to_string(), // Compaction prompt is synthesized; no UI element ranges to preserve. text_elements: Vec::new(), diff --git a/codex-rs/core/src/tools/handlers/agent_jobs.rs b/codex-rs/core/src/tools/handlers/agent_jobs.rs index 4c3cd34c2ea..789a8ffd3a9 100644 --- a/codex-rs/core/src/tools/handlers/agent_jobs.rs +++ b/codex-rs/core/src/tools/handlers/agent_jobs.rs @@ -198,6 +198,7 @@ async fn run_agent_job_loop( for item in pending_items { let prompt = build_worker_prompt(&job, &item)?; let items = vec![UserInput::Text { + client_id: None, text: prompt, text_elements: Vec::new(), }]; diff --git a/codex-rs/core/src/tools/handlers/multi_agents_common.rs b/codex-rs/core/src/tools/handlers/multi_agents_common.rs index f1e7bcd429b..117dd6225ea 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents_common.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents_common.rs @@ -179,6 +179,7 @@ pub(crate) fn parse_collab_input( )); } Ok(vec![UserInput::Text { + client_id: None, text: message, text_elements: Vec::new(), }] diff --git a/codex-rs/core/src/tools/handlers/multi_agents_tests.rs b/codex-rs/core/src/tools/handlers/multi_agents_tests.rs index 164d5410e0b..aa4904465ce 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents_tests.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents_tests.rs @@ -1360,6 +1360,7 @@ async fn multi_agent_v2_send_message_accepts_root_target_from_child() { .spawn_agent_with_metadata( (*turn.config).clone(), vec![UserInput::Text { + client_id: None, text: "inspect this repo".to_string(), text_elements: Vec::new(), }] @@ -1436,6 +1437,7 @@ async fn multi_agent_v2_followup_task_rejects_root_target_from_child() { .spawn_agent_with_metadata( (*turn.config).clone(), vec![UserInput::Text { + client_id: None, text: "inspect this repo".to_string(), text_elements: Vec::new(), }] @@ -1609,6 +1611,7 @@ async fn multi_agent_v2_list_agents_filters_by_relative_path_prefix() { .spawn_agent_with_metadata( config.clone(), vec![UserInput::Text { + client_id: None, text: "research".to_string(), text_elements: Vec::new(), }] @@ -1630,6 +1633,7 @@ async fn multi_agent_v2_list_agents_filters_by_relative_path_prefix() { .spawn_agent_with_metadata( config, vec![UserInput::Text { + client_id: None, text: "build".to_string(), text_elements: Vec::new(), }] @@ -2583,10 +2587,12 @@ async fn send_input_accepts_structured_items() { environments: None, items: vec![ UserInput::Mention { + client_id: None, name: "drive".to_string(), path: "app://google_drive".to_string(), }, UserInput::Text { + client_id: None, text: "read the folder".to_string(), text_elements: Vec::new(), }, diff --git a/codex-rs/core/tests/common/test_codex.rs b/codex-rs/core/tests/common/test_codex.rs index 73e9e0ece88..c5d762a667e 100644 --- a/codex-rs/core/tests/common/test_codex.rs +++ b/codex-rs/core/tests/common/test_codex.rs @@ -761,6 +761,7 @@ impl TestCodex { self.codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: prompt.into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/abort_tasks.rs b/codex-rs/core/tests/suite/abort_tasks.rs index 82bb8c87987..94aaad314fc 100644 --- a/codex-rs/core/tests/suite/abort_tasks.rs +++ b/codex-rs/core/tests/suite/abort_tasks.rs @@ -48,6 +48,7 @@ async fn interrupt_long_running_tool_emits_turn_aborted() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "start sleep".into(), text_elements: Vec::new(), }], @@ -106,6 +107,7 @@ async fn interrupt_tool_records_history_entries() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "start history recording".into(), text_elements: Vec::new(), }], @@ -128,6 +130,7 @@ async fn interrupt_tool_records_history_entries() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "follow up".into(), text_elements: Vec::new(), }], @@ -212,6 +215,7 @@ async fn interrupt_persists_turn_aborted_marker_in_next_request() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "start interrupt marker".into(), text_elements: Vec::new(), }], @@ -234,6 +238,7 @@ async fn interrupt_persists_turn_aborted_marker_in_next_request() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "follow up".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/additional_context.rs b/codex-rs/core/tests/suite/additional_context.rs index f9bfdb966b6..374e727f419 100644 --- a/codex-rs/core/tests/suite/additional_context.rs +++ b/codex-rs/core/tests/suite/additional_context.rs @@ -39,6 +39,7 @@ async fn additional_context_is_model_visible_but_not_a_user_message_item() -> Re .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "inspect the active tab".to_string(), text_elements: Vec::new(), }], @@ -75,6 +76,7 @@ async fn additional_context_is_model_visible_but_not_a_user_message_item() -> Re assert_eq!( user_item.content, vec![UserInput::Text { + client_id: None, text: "inspect the active tab".to_string(), text_elements: Vec::new(), }] @@ -130,6 +132,7 @@ async fn external_context_like_user_text_remains_a_user_message_item() -> Result .build(&server) .await?; let user_input = UserInput::Text { + client_id: None, text: "".to_string(), text_elements: Vec::new(), }; @@ -184,6 +187,7 @@ async fn additional_context_trust_controls_message_role() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "inspect context".to_string(), text_elements: Vec::new(), }], @@ -265,6 +269,7 @@ async fn additional_context_is_deduplicated_between_turns_while_retained() -> Re .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "first turn".to_string(), text_elements: Vec::new(), }], @@ -283,6 +288,7 @@ async fn additional_context_is_deduplicated_between_turns_while_retained() -> Re .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "second turn".to_string(), text_elements: Vec::new(), }], @@ -345,6 +351,7 @@ async fn additional_context_removes_one_value_while_adding_another() -> Result<( .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "first turn".to_string(), text_elements: Vec::new(), }], @@ -378,6 +385,7 @@ async fn additional_context_removes_one_value_while_adding_another() -> Result<( .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "second turn".to_string(), text_elements: Vec::new(), }], @@ -411,6 +419,7 @@ async fn additional_context_removes_one_value_while_adding_another() -> Result<( .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "third turn".to_string(), text_elements: Vec::new(), }], @@ -508,6 +517,7 @@ async fn additional_context_values_are_truncated_before_model_input() -> Result< .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "summarize context".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/apply_patch_cli.rs b/codex-rs/core/tests/suite/apply_patch_cli.rs index da138851f55..8835fa639e2 100644 --- a/codex-rs/core/tests/suite/apply_patch_cli.rs +++ b/codex-rs/core/tests/suite/apply_patch_cli.rs @@ -85,6 +85,7 @@ async fn submit_without_wait_with_turn_permissions( test.codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: prompt.into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/approvals.rs b/codex-rs/core/tests/suite/approvals.rs index e16a4a14a20..c0061058162 100644 --- a/codex-rs/core/tests/suite/approvals.rs +++ b/codex-rs/core/tests/suite/approvals.rs @@ -647,6 +647,7 @@ async fn submit_turn( test.codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: prompt.into(), text_elements: Vec::new(), }], @@ -2593,6 +2594,7 @@ async fn matched_prefix_rule_runs_unsandboxed_under_zsh_fork() -> Result<()> { test.codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: "run allowed touch under zsh fork".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/client.rs b/codex-rs/core/tests/suite/client.rs index 7234c59c0fd..d6449dc85fd 100644 --- a/codex-rs/core/tests/suite/client.rs +++ b/codex-rs/core/tests/suite/client.rs @@ -387,6 +387,7 @@ async fn resume_includes_initial_messages_and_sends_prior_items() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -754,6 +755,7 @@ async fn includes_session_id_thread_id_and_model_headers_in_request() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -966,6 +968,7 @@ async fn includes_base_instructions_override_in_request() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -1024,6 +1027,7 @@ async fn chatgpt_auth_sends_correct_request() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -1149,6 +1153,7 @@ async fn prefers_apikey_when_config_prefers_apikey_even_with_chatgpt_tokens() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -1189,6 +1194,7 @@ async fn includes_user_instructions_message_in_request() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -1278,6 +1284,7 @@ async fn includes_apps_guidance_as_developer_message_for_chatgpt_auth() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -1342,6 +1349,7 @@ async fn omits_apps_guidance_for_api_key_auth_even_when_feature_enabled() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -1402,6 +1410,7 @@ async fn omits_apps_guidance_when_configured_off() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -1445,6 +1454,7 @@ async fn omits_environment_context_when_configured_off() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -1503,6 +1513,7 @@ async fn skills_append_to_developer_message() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -1587,6 +1598,7 @@ async fn skills_use_aliases_in_developer_message_under_budget_pressure() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -1649,6 +1661,7 @@ async fn includes_configured_effort_in_request() -> anyhow::Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -1692,6 +1705,7 @@ async fn includes_no_effort_in_request() -> anyhow::Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -1736,6 +1750,7 @@ async fn includes_default_reasoning_effort_in_request_when_defined_by_model_info .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -1787,6 +1802,7 @@ async fn user_turn_collaboration_mode_overrides_model_and_effort() -> anyhow::Re codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -1845,6 +1861,7 @@ async fn configured_reasoning_summary_is_sent() -> anyhow::Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -1910,6 +1927,7 @@ async fn user_turn_explicit_reasoning_summary_overrides_model_catalog_default() codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -1972,6 +1990,7 @@ async fn reasoning_summary_is_omitted_when_disabled() -> anyhow::Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -2032,6 +2051,7 @@ async fn reasoning_summary_none_overrides_model_catalog_default() -> anyhow::Res .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -2072,6 +2092,7 @@ async fn includes_default_verbosity_in_request() -> anyhow::Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -2121,6 +2142,7 @@ async fn configured_verbosity_not_sent_for_models_without_support() -> anyhow::R .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -2169,6 +2191,7 @@ async fn configured_verbosity_is_sent() -> anyhow::Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -2222,6 +2245,7 @@ async fn includes_developer_instructions_message_in_request() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -2519,6 +2543,7 @@ async fn token_count_includes_rate_limits_snapshot() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -2659,6 +2684,7 @@ async fn usage_limit_error_emits_rate_limit_event() -> anyhow::Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -2737,6 +2763,7 @@ async fn context_window_error_sets_total_tokens_to_model_window() -> anyhow::Res .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "seed turn".into(), text_elements: Vec::new(), }], @@ -2753,6 +2780,7 @@ async fn context_window_error_sets_total_tokens_to_model_window() -> anyhow::Res .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "trigger context window".into(), text_elements: Vec::new(), }], @@ -2839,6 +2867,7 @@ async fn incomplete_response_emits_content_filter_error_message() -> anyhow::Res .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "trigger incomplete".into(), text_elements: Vec::new(), }], @@ -2951,6 +2980,7 @@ async fn azure_overrides_assign_properties_used_for_responses_url() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -3041,6 +3071,7 @@ async fn env_var_overrides_loaded_auth() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -3099,6 +3130,7 @@ async fn history_dedupes_streamed_and_final_messages_across_turns() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "U1".into(), text_elements: Vec::new(), }], @@ -3116,6 +3148,7 @@ async fn history_dedupes_streamed_and_final_messages_across_turns() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "U2".into(), text_elements: Vec::new(), }], @@ -3133,6 +3166,7 @@ async fn history_dedupes_streamed_and_final_messages_across_turns() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "U3".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/client_websockets.rs b/codex-rs/core/tests/suite/client_websockets.rs index 09d0093f990..5d7721615a6 100755 --- a/codex-rs/core/tests/suite/client_websockets.rs +++ b/codex-rs/core/tests/suite/client_websockets.rs @@ -1314,6 +1314,7 @@ async fn responses_websocket_usage_limit_error_emits_rate_limit_event() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -1404,6 +1405,7 @@ async fn responses_websocket_invalid_request_error_with_status_is_forwarded() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/code_mode.rs b/codex-rs/core/tests/suite/code_mode.rs index 90daf219632..b77ff9963a0 100644 --- a/codex-rs/core/tests/suite/code_mode.rs +++ b/codex-rs/core/tests/suite/code_mode.rs @@ -2985,6 +2985,7 @@ text( test.codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: "use exec to inspect and call hidden tools".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/collaboration_instructions.rs b/codex-rs/core/tests/suite/collaboration_instructions.rs index bbbb0f1aa23..88512bed52c 100644 --- a/codex-rs/core/tests/suite/collaboration_instructions.rs +++ b/codex-rs/core/tests/suite/collaboration_instructions.rs @@ -74,6 +74,7 @@ async fn no_collaboration_instructions_by_default() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -129,6 +130,7 @@ async fn user_input_includes_collaboration_instructions_after_override() -> Resu .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -166,6 +168,7 @@ async fn collaboration_instructions_added_on_user_turn() -> Result<()> { test.codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -217,6 +220,7 @@ async fn collaboration_instructions_omitted_when_disabled() -> Result<()> { test.codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -278,6 +282,7 @@ async fn override_then_next_turn_uses_updated_collaboration_instructions() -> Re .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -326,6 +331,7 @@ async fn user_turn_overrides_collaboration_instructions_after_override() -> Resu test.codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -392,6 +398,7 @@ async fn collaboration_mode_update_emits_new_instruction_message() -> Result<()> .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello 1".into(), text_elements: Vec::new(), }], @@ -416,6 +423,7 @@ async fn collaboration_mode_update_emits_new_instruction_message() -> Result<()> .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello 2".into(), text_elements: Vec::new(), }], @@ -469,6 +477,7 @@ async fn collaboration_mode_update_noop_does_not_append() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello 1".into(), text_elements: Vec::new(), }], @@ -493,6 +502,7 @@ async fn collaboration_mode_update_noop_does_not_append() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello 2".into(), text_elements: Vec::new(), }], @@ -548,6 +558,7 @@ async fn collaboration_mode_update_emits_new_instruction_message_when_mode_chang .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello 1".into(), text_elements: Vec::new(), }], @@ -575,6 +586,7 @@ async fn collaboration_mode_update_emits_new_instruction_message_when_mode_chang .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello 2".into(), text_elements: Vec::new(), }], @@ -631,6 +643,7 @@ async fn collaboration_mode_update_noop_does_not_append_when_mode_is_unchanged() .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello 1".into(), text_elements: Vec::new(), }], @@ -658,6 +671,7 @@ async fn collaboration_mode_update_noop_does_not_append_when_mode_is_unchanged() .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello 2".into(), text_elements: Vec::new(), }], @@ -717,6 +731,7 @@ async fn resume_replays_collaboration_instructions() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -734,6 +749,7 @@ async fn resume_replays_collaboration_instructions() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "after resume".into(), text_elements: Vec::new(), }], @@ -787,6 +803,7 @@ async fn empty_collaboration_instructions_are_ignored() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/compact.rs b/codex-rs/core/tests/suite/compact.rs index 0e2c02de917..197434d2e34 100644 --- a/codex-rs/core/tests/suite/compact.rs +++ b/codex-rs/core/tests/suite/compact.rs @@ -91,6 +91,7 @@ fn disabled_permission_user_turn(text: impl Into, cwd: PathBuf, model: S turn_permission_fields(PermissionProfile::Disabled, cwd.as_path()); Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: text.into(), text_elements: Vec::new(), }], @@ -404,6 +405,7 @@ async fn summarize_context_three_requests_and_instructions() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello world".into(), text_elements: Vec::new(), }], @@ -430,6 +432,7 @@ async fn summarize_context_three_requests_and_instructions() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: THIRD_USER_MSG.into(), text_elements: Vec::new(), }], @@ -606,6 +609,7 @@ async fn manual_pre_compact_block_decision_does_not_block_compaction() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello before blocked compact".to_string(), text_elements: Vec::new(), }], @@ -680,6 +684,7 @@ async fn compact_hooks_respect_matchers_and_post_runs_after_compaction() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello before matched compact".to_string(), text_elements: Vec::new(), }], @@ -751,6 +756,7 @@ async fn manual_compact_uses_custom_prompt() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "USER_ONE".to_string(), text_elements: Vec::new(), }], @@ -899,6 +905,7 @@ async fn manual_compact_emits_context_compaction_items() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "manual compact".into(), text_elements: Vec::new(), }], @@ -1066,6 +1073,7 @@ async fn multiple_auto_compact_per_task_runs_after_token_limit_hit() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: user_message.into(), text_elements: Vec::new(), }], @@ -1539,6 +1547,7 @@ async fn auto_compact_runs_after_token_limit_hit() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: FIRST_AUTO_MSG.into(), text_elements: Vec::new(), }], @@ -1556,6 +1565,7 @@ async fn auto_compact_runs_after_token_limit_hit() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: SECOND_AUTO_MSG.into(), text_elements: Vec::new(), }], @@ -1573,6 +1583,7 @@ async fn auto_compact_runs_after_token_limit_hit() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: POST_AUTO_USER_MSG.into(), text_elements: Vec::new(), }], @@ -1745,6 +1756,7 @@ async fn auto_compact_emits_context_compaction_items() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: user.into(), text_elements: Vec::new(), }], @@ -1827,6 +1839,7 @@ async fn auto_compact_starts_after_turn_started() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: FIRST_AUTO_MSG.into(), text_elements: Vec::new(), }], @@ -1843,6 +1856,7 @@ async fn auto_compact_starts_after_turn_started() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: SECOND_AUTO_MSG.into(), text_elements: Vec::new(), }], @@ -1859,6 +1873,7 @@ async fn auto_compact_starts_after_turn_started() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: POST_AUTO_USER_MSG.into(), text_elements: Vec::new(), }], @@ -2388,6 +2403,7 @@ async fn auto_compact_persists_rollout_entries() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: FIRST_AUTO_MSG.into(), text_elements: Vec::new(), }], @@ -2404,6 +2420,7 @@ async fn auto_compact_persists_rollout_entries() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: SECOND_AUTO_MSG.into(), text_elements: Vec::new(), }], @@ -2420,6 +2437,7 @@ async fn auto_compact_persists_rollout_entries() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: POST_AUTO_USER_MSG.into(), text_elements: Vec::new(), }], @@ -2510,6 +2528,7 @@ async fn manual_compact_retries_after_context_window_error() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "first turn".into(), text_elements: Vec::new(), }], @@ -2615,6 +2634,7 @@ async fn manual_compact_non_context_failure_retries_then_emits_task_error() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "first turn".into(), text_elements: Vec::new(), }], @@ -2711,6 +2731,7 @@ async fn manual_compact_twice_preserves_latest_user_messages() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: first_user_message.into(), text_elements: Vec::new(), }], @@ -2730,6 +2751,7 @@ async fn manual_compact_twice_preserves_latest_user_messages() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: second_user_message.into(), text_elements: Vec::new(), }], @@ -2749,6 +2771,7 @@ async fn manual_compact_twice_preserves_latest_user_messages() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: final_user_message.into(), text_elements: Vec::new(), }], @@ -2961,6 +2984,7 @@ async fn auto_compact_allows_multiple_attempts_when_interleaved_with_other_turn_ .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: user.into(), text_elements: Vec::new(), }], @@ -3067,6 +3091,7 @@ async fn snapshot_request_shape_mid_turn_continuation_compaction() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: FUNCTION_CALL_LIMIT_MSG.into(), text_elements: Vec::new(), }], @@ -3499,6 +3524,7 @@ async fn auto_compact_counts_encrypted_reasoning_before_last_user() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: user.into(), text_elements: Vec::new(), }], @@ -3619,6 +3645,7 @@ async fn auto_compact_runs_when_reasoning_header_clears_between_turns() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: user.into(), text_elements: Vec::new(), }], @@ -3682,6 +3709,7 @@ async fn snapshot_request_shape_pre_turn_compaction_including_incoming_user_mess .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: user.to_string(), text_elements: Vec::new(), }], @@ -3710,10 +3738,12 @@ async fn snapshot_request_shape_pre_turn_compaction_including_incoming_user_mess environments: None, items: vec![ UserInput::Image { + client_id: None, image_url: image_url.clone(), detail: None, }, UserInput::Text { + client_id: None, text: "USER_THREE".to_string(), text_elements: Vec::new(), }, @@ -3905,6 +3935,7 @@ async fn snapshot_request_shape_pre_turn_compaction_context_window_exceeded() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "USER_ONE".to_string(), text_elements: Vec::new(), }], @@ -3921,6 +3952,7 @@ async fn snapshot_request_shape_pre_turn_compaction_context_window_exceeded() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "USER_TWO".to_string(), text_elements: Vec::new(), }], @@ -3995,6 +4027,7 @@ async fn snapshot_request_shape_manual_compact_without_previous_user_messages() .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "AFTER_MANUAL_EMPTY_COMPACT".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/compact_remote.rs b/codex-rs/core/tests/suite/compact_remote.rs index 1c5b80f35f9..c62f7143ec1 100644 --- a/codex-rs/core/tests/suite/compact_remote.rs +++ b/codex-rs/core/tests/suite/compact_remote.rs @@ -321,6 +321,7 @@ async fn remote_compact_replaces_history_for_followups() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello remote compact".into(), text_elements: Vec::new(), }], @@ -339,6 +340,7 @@ async fn remote_compact_replaces_history_for_followups() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "after compact".into(), text_elements: Vec::new(), }], @@ -582,6 +584,7 @@ async fn assert_remote_manual_compact_request_parity( .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "TURN_ONE_USER".to_string(), text_elements: Vec::new(), }], @@ -598,10 +601,12 @@ async fn assert_remote_manual_compact_request_parity( environments: None, items: vec![ UserInput::Text { + client_id: None, text: "TURN_TWO_PREFIX".to_string(), text_elements: Vec::new(), }, UserInput::Text { + client_id: None, text: "TURN_TWO_SUFFIX".to_string(), text_elements: Vec::new(), }, @@ -618,6 +623,7 @@ async fn assert_remote_manual_compact_request_parity( .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "TURN_THREE_TOOL_USER".to_string(), text_elements: Vec::new(), }], @@ -634,10 +640,12 @@ async fn assert_remote_manual_compact_request_parity( environments: None, items: vec![ UserInput::Image { + client_id: None, image_url, detail: None, }, UserInput::Text { + client_id: None, text: "TURN_FOUR_IMAGE_USER".to_string(), text_elements: Vec::new(), }, @@ -654,6 +662,7 @@ async fn assert_remote_manual_compact_request_parity( .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "TURN_FIVE_USER".to_string(), text_elements: Vec::new(), }], @@ -823,6 +832,7 @@ async fn remote_compact_v2_reuses_compaction_trigger_for_followups() -> Result<( .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello remote compact".into(), text_elements: Vec::new(), }], @@ -841,6 +851,7 @@ async fn remote_compact_v2_reuses_compaction_trigger_for_followups() -> Result<( .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "after compact".into(), text_elements: Vec::new(), }], @@ -969,6 +980,7 @@ async fn remote_compact_v2_retries_failures_with_stream_retry_budget() -> Result .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello remote compact".into(), text_elements: Vec::new(), }], @@ -987,6 +999,7 @@ async fn remote_compact_v2_retries_failures_with_stream_retry_budget() -> Result .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "after compact".into(), text_elements: Vec::new(), }], @@ -1074,6 +1087,7 @@ async fn remote_compact_v2_accepts_additional_output_items_before_compaction() - .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello remote compact".into(), text_elements: Vec::new(), }], @@ -1092,6 +1106,7 @@ async fn remote_compact_v2_accepts_additional_output_items_before_compaction() - .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "after compact".into(), text_elements: Vec::new(), }], @@ -1184,6 +1199,7 @@ async fn remote_compact_filters_deferred_dynamic_tools() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello remote compact".into(), text_elements: Vec::new(), }], @@ -1257,6 +1273,7 @@ async fn remote_compact_runs_automatically() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello remote compact".into(), text_elements: Vec::new(), }], @@ -1395,6 +1412,7 @@ async fn remote_compact_trims_function_call_history_to_fit_context_window() -> R .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: first_user_message.into(), text_elements: Vec::new(), }], @@ -1410,6 +1428,7 @@ async fn remote_compact_trims_function_call_history_to_fit_context_window() -> R .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: second_user_message.into(), text_elements: Vec::new(), }], @@ -1529,6 +1548,7 @@ async fn auto_remote_compact_trims_function_call_history_to_fit_context_window() .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: first_user_message.into(), text_elements: Vec::new(), }], @@ -1544,6 +1564,7 @@ async fn auto_remote_compact_trims_function_call_history_to_fit_context_window() .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: second_user_message.into(), text_elements: Vec::new(), }], @@ -1565,6 +1586,7 @@ async fn auto_remote_compact_trims_function_call_history_to_fit_context_window() .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "turn that triggers auto compact".into(), text_elements: Vec::new(), }], @@ -1666,6 +1688,7 @@ async fn auto_remote_compact_failure_stops_agent_loop() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "turn that exceeds token threshold".into(), text_elements: Vec::new(), }], @@ -1681,6 +1704,7 @@ async fn auto_remote_compact_failure_stops_agent_loop() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "turn that triggers auto compact".into(), text_elements: Vec::new(), }], @@ -1776,6 +1800,7 @@ async fn remote_compact_trim_estimate_uses_session_base_instructions() -> Result .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: first_user_message.into(), text_elements: Vec::new(), }], @@ -1794,6 +1819,7 @@ async fn remote_compact_trim_estimate_uses_session_base_instructions() -> Result .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: second_user_message.into(), text_elements: Vec::new(), }], @@ -1886,6 +1912,7 @@ async fn remote_compact_trim_estimate_uses_session_base_instructions() -> Result .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: first_user_message.into(), text_elements: Vec::new(), }], @@ -1904,6 +1931,7 @@ async fn remote_compact_trim_estimate_uses_session_base_instructions() -> Result .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: second_user_message.into(), text_elements: Vec::new(), }], @@ -1976,6 +2004,7 @@ async fn remote_manual_compact_emits_context_compaction_items() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "manual remote compact".into(), text_elements: Vec::new(), }], @@ -2058,6 +2087,7 @@ async fn remote_manual_compact_failure_emits_task_error_event() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "manual remote compact".into(), text_elements: Vec::new(), }], @@ -2143,6 +2173,7 @@ async fn remote_compact_persists_replacement_history_in_rollout() -> Result<()> .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "needs compaction".into(), text_elements: Vec::new(), }], @@ -2287,6 +2318,7 @@ async fn remote_compact_and_resume_refresh_stale_developer_instructions() -> Res .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "start remote compact flow".into(), text_elements: Vec::new(), }], @@ -2306,6 +2338,7 @@ async fn remote_compact_and_resume_refresh_stale_developer_instructions() -> Res .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "after compact in same session".into(), text_elements: Vec::new(), }], @@ -2332,6 +2365,7 @@ async fn remote_compact_and_resume_refresh_stale_developer_instructions() -> Res .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "after resume".into(), text_elements: Vec::new(), }], @@ -2429,6 +2463,7 @@ async fn remote_compact_refreshes_stale_developer_instructions_without_resume() .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "start remote compact flow".into(), text_elements: Vec::new(), }], @@ -2447,6 +2482,7 @@ async fn remote_compact_refreshes_stale_developer_instructions_without_resume() .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "after compact in same session".into(), text_elements: Vec::new(), }], @@ -2520,6 +2556,7 @@ async fn snapshot_request_shape_remote_pre_turn_compaction_restates_realtime_sta .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "USER_ONE".to_string(), text_elements: Vec::new(), }], @@ -2535,6 +2572,7 @@ async fn snapshot_request_shape_remote_pre_turn_compaction_restates_realtime_sta .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "USER_TWO".to_string(), text_elements: Vec::new(), }], @@ -2603,6 +2641,7 @@ async fn remote_request_uses_custom_experimental_realtime_start_instructions() - .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "USER_ONE".to_string(), text_elements: Vec::new(), }], @@ -2665,6 +2704,7 @@ async fn snapshot_request_shape_remote_pre_turn_compaction_restates_realtime_end .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "USER_ONE".to_string(), text_elements: Vec::new(), }], @@ -2682,6 +2722,7 @@ async fn snapshot_request_shape_remote_pre_turn_compaction_restates_realtime_end .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "USER_TWO".to_string(), text_elements: Vec::new(), }], @@ -2758,6 +2799,7 @@ async fn snapshot_request_shape_remote_manual_compact_restates_realtime_start() .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "USER_ONE".to_string(), text_elements: Vec::new(), }], @@ -2776,6 +2818,7 @@ async fn snapshot_request_shape_remote_manual_compact_restates_realtime_start() .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "USER_TWO".to_string(), text_elements: Vec::new(), }], @@ -2860,6 +2903,7 @@ async fn snapshot_request_shape_remote_mid_turn_compaction_does_not_restate_real .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "SETUP_USER".to_string(), text_elements: Vec::new(), }], @@ -2877,6 +2921,7 @@ async fn snapshot_request_shape_remote_mid_turn_compaction_does_not_restate_real .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "USER_TWO".to_string(), text_elements: Vec::new(), }], @@ -2969,6 +3014,7 @@ async fn snapshot_request_shape_remote_compact_resume_restates_realtime_end() -> .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "USER_ONE".to_string(), text_elements: Vec::new(), }], @@ -3000,6 +3046,7 @@ async fn snapshot_request_shape_remote_compact_resume_restates_realtime_end() -> .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "USER_TWO".to_string(), text_elements: Vec::new(), }], @@ -3090,6 +3137,7 @@ async fn snapshot_request_shape_remote_pre_turn_compaction_including_incoming_us .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: user.to_string(), text_elements: Vec::new(), }], @@ -3178,6 +3226,7 @@ async fn snapshot_request_shape_remote_pre_turn_compaction_strips_incoming_model .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "BEFORE_SWITCH_USER".to_string(), text_elements: Vec::new(), }], @@ -3201,6 +3250,7 @@ async fn snapshot_request_shape_remote_pre_turn_compaction_strips_incoming_model .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "AFTER_SWITCH_USER".to_string(), text_elements: Vec::new(), }], @@ -3321,6 +3371,7 @@ async fn snapshot_request_shape_remote_pre_turn_compaction_context_window_exceed .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "USER_ONE".to_string(), text_elements: Vec::new(), }], @@ -3336,6 +3387,7 @@ async fn snapshot_request_shape_remote_pre_turn_compaction_context_window_exceed .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "USER_TWO".to_string(), text_elements: Vec::new(), }], @@ -3422,6 +3474,7 @@ async fn snapshot_request_shape_remote_mid_turn_continuation_compaction() -> Res .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "USER_ONE".to_string(), text_elements: Vec::new(), }], @@ -3501,6 +3554,7 @@ async fn snapshot_request_shape_remote_mid_turn_compaction_summary_only_reinject .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "USER_ONE".to_string(), text_elements: Vec::new(), }], @@ -3588,6 +3642,7 @@ async fn snapshot_request_shape_remote_mid_turn_compaction_multi_summary_reinjec .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "USER_ONE".to_string(), text_elements: Vec::new(), }], @@ -3606,6 +3661,7 @@ async fn snapshot_request_shape_remote_mid_turn_compaction_multi_summary_reinjec .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "USER_TWO".to_string(), text_elements: Vec::new(), }], @@ -3689,6 +3745,7 @@ async fn snapshot_request_shape_remote_manual_compact_without_previous_user_mess .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "USER_ONE".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/compact_remote_parity.rs b/codex-rs/core/tests/suite/compact_remote_parity.rs index 22678fc46f7..8ad0ff55806 100644 --- a/codex-rs/core/tests/suite/compact_remote_parity.rs +++ b/codex-rs/core/tests/suite/compact_remote_parity.rs @@ -319,6 +319,7 @@ async fn run_manual_session( submit_user_input( &codex, vec![UserInput::Text { + client_id: None, text: format!("{}_AFTER_COMPACT_USER", scenario.name), text_elements: Vec::new(), }], @@ -369,6 +370,7 @@ async fn run_pre_turn_auto_session(mode: Mode) -> Result { submit_user_input( &codex, vec![UserInput::Text { + client_id: None, text: "pre_turn_auto_before".to_string(), text_elements: Vec::new(), }], @@ -377,6 +379,7 @@ async fn run_pre_turn_auto_session(mode: Mode) -> Result { submit_user_input( &codex, vec![UserInput::Text { + client_id: None, text: "pre_turn_auto_after".to_string(), text_elements: Vec::new(), }], @@ -427,6 +430,7 @@ async fn run_mid_turn_auto_session(mode: Mode) -> Result { submit_user_input( &codex, vec![UserInput::Text { + client_id: None, text: "mid_turn_auto_user".to_string(), text_elements: Vec::new(), }], @@ -466,6 +470,7 @@ async fn run_manual_hook_session(mode: Mode) -> Result { submit_user_input( &codex, vec![UserInput::Text { + client_id: None, text: "manual_hooks_before".to_string(), text_elements: Vec::new(), }], @@ -622,11 +627,13 @@ fn user_input_for_step(scenario_name: &str, idx: usize, step: Step) -> Vec, text: &str) { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: text.into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/exec_policy.rs b/codex-rs/core/tests/suite/exec_policy.rs index 9e350d27730..1c19bfd5c61 100644 --- a/codex-rs/core/tests/suite/exec_policy.rs +++ b/codex-rs/core/tests/suite/exec_policy.rs @@ -48,6 +48,7 @@ async fn submit_user_turn( test.codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: prompt.into(), text_elements: Vec::new(), }], @@ -136,6 +137,7 @@ async fn execpolicy_blocks_shell_invocation() -> Result<()> { test.codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: "run shell command".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/fork_thread.rs b/codex-rs/core/tests/suite/fork_thread.rs index 544c530aa08..b51e7b28072 100644 --- a/codex-rs/core/tests/suite/fork_thread.rs +++ b/codex-rs/core/tests/suite/fork_thread.rs @@ -52,6 +52,7 @@ async fn fork_thread_twice_drops_to_first_message() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: text.to_string(), text_elements: Vec::new(), }], @@ -177,6 +178,7 @@ async fn fork_thread_from_history_does_not_require_source_rollout_path() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "fork me from stored history".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/hooks.rs b/codex-rs/core/tests/suite/hooks.rs index a535eb4b8e2..aa6b167fe0c 100644 --- a/codex-rs/core/tests/suite/hooks.rs +++ b/codex-rs/core/tests/suite/hooks.rs @@ -1845,6 +1845,7 @@ async fn blocked_queued_prompt_does_not_strand_earlier_accepted_prompt() -> Resu .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "initial prompt".to_string(), text_elements: Vec::new(), }], @@ -1865,6 +1866,7 @@ async fn blocked_queued_prompt_does_not_strand_earlier_accepted_prompt() -> Resu .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: text.to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/image_rollout.rs b/codex-rs/core/tests/suite/image_rollout.rs index 87eb03079e8..8ead712911a 100644 --- a/codex-rs/core/tests/suite/image_rollout.rs +++ b/codex-rs/core/tests/suite/image_rollout.rs @@ -116,10 +116,12 @@ async fn copy_paste_local_image_persists_rollout_request_shape() -> anyhow::Resu .submit(Op::UserInput { items: vec![ UserInput::LocalImage { + client_id: None, path: abs_path.clone(), detail: None, }, UserInput::Text { + client_id: None, text: "pasted image".to_string(), text_elements: Vec::new(), }, @@ -213,10 +215,12 @@ async fn drag_drop_image_persists_rollout_request_shape() -> anyhow::Result<()> .submit(Op::UserInput { items: vec![ UserInput::Image { + client_id: None, image_url: image_url.clone(), detail: None, }, UserInput::Text { + client_id: None, text: "dropped image".to_string(), text_elements: Vec::new(), }, diff --git a/codex-rs/core/tests/suite/items.rs b/codex-rs/core/tests/suite/items.rs index a56b2ab95f5..beabe0c9381 100644 --- a/codex-rs/core/tests/suite/items.rs +++ b/codex-rs/core/tests/suite/items.rs @@ -52,6 +52,7 @@ fn disabled_plan_turn( turn_permission_fields(PermissionProfile::Disabled, cwd.as_path()); Ok(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: text.into(), text_elements: Vec::new(), }], @@ -110,6 +111,7 @@ async fn user_message_item_is_emitted() -> anyhow::Result<()> { Some("".into()), )]; let expected_input = UserInput::Text { + client_id: None, text: "please inspect sample.txt".into(), text_elements: text_elements.clone(), }; @@ -175,6 +177,7 @@ async fn assistant_message_item_is_emitted() -> anyhow::Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "please summarize results".into(), text_elements: Vec::new(), }], @@ -237,6 +240,7 @@ async fn reasoning_item_is_emitted() -> anyhow::Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "explain your reasoning".into(), text_elements: Vec::new(), }], @@ -300,6 +304,7 @@ async fn web_search_item_is_emitted() -> anyhow::Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "find the weather".into(), text_elements: Vec::new(), }], @@ -381,6 +386,7 @@ async fn image_generation_call_event_is_emitted() -> anyhow::Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "generate a tiny blue square".into(), text_elements: Vec::new(), }], @@ -469,6 +475,7 @@ async fn image_generation_call_event_is_emitted_when_image_save_fails() -> anyho .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "generate an image".into(), text_elements: Vec::new(), }], @@ -526,6 +533,7 @@ async fn agent_message_content_delta_has_item_metadata() -> anyhow::Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "please stream text".into(), text_elements: Vec::new(), }], @@ -1111,6 +1119,7 @@ async fn reasoning_content_delta_has_item_metadata() -> anyhow::Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "reason through it".into(), text_elements: Vec::new(), }], @@ -1167,6 +1176,7 @@ async fn reasoning_raw_content_delta_respects_flag() -> anyhow::Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "show raw reasoning".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/json_result.rs b/codex-rs/core/tests/suite/json_result.rs index 67275d31468..eb7e4c42cdf 100644 --- a/codex-rs/core/tests/suite/json_result.rs +++ b/codex-rs/core/tests/suite/json_result.rs @@ -77,6 +77,7 @@ async fn codex_returns_json_result(model: String) -> anyhow::Result<()> { codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: "hello world".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/mcp_turn_metadata.rs b/codex-rs/core/tests/suite/mcp_turn_metadata.rs index 897d8621628..475751ce394 100644 --- a/codex-rs/core/tests/suite/mcp_turn_metadata.rs +++ b/codex-rs/core/tests/suite/mcp_turn_metadata.rs @@ -70,6 +70,7 @@ async fn submit_user_turn( test.codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: text.to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/model_switching.rs b/codex-rs/core/tests/suite/model_switching.rs index 3c4dd40b32f..c9f5c17fc00 100644 --- a/codex-rs/core/tests/suite/model_switching.rs +++ b/codex-rs/core/tests/suite/model_switching.rs @@ -156,6 +156,7 @@ async fn model_change_appends_model_instructions_developer_message() -> Result<( .submit(read_only_user_turn( &test, vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -177,6 +178,7 @@ async fn model_change_appends_model_instructions_developer_message() -> Result<( .submit(read_only_user_turn( &test, vec![UserInput::Text { + client_id: None, text: "switch models".into(), text_elements: Vec::new(), }], @@ -228,6 +230,7 @@ async fn model_and_personality_change_only_appends_model_instructions() -> Resul .submit(read_only_user_turn( &test, vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -250,6 +253,7 @@ async fn model_and_personality_change_only_appends_model_instructions() -> Resul .submit(read_only_user_turn( &test, vec![UserInput::Text { + client_id: None, text: "switch model and personality".into(), text_elements: Vec::new(), }], @@ -509,10 +513,12 @@ async fn model_change_from_image_to_text_strips_prior_image_content() -> Result< &test, vec![ UserInput::Image { + client_id: None, image_url: image_url.clone(), detail: None, }, UserInput::Text { + client_id: None, text: "first turn".to_string(), text_elements: Vec::new(), }, @@ -526,6 +532,7 @@ async fn model_change_from_image_to_text_strips_prior_image_content() -> Result< .submit(read_only_user_turn( &test, vec![UserInput::Text { + client_id: None, text: "second turn".to_string(), text_elements: Vec::new(), }], @@ -612,6 +619,7 @@ async fn generated_image_is_replayed_for_image_capable_models() -> Result<()> { .submit(read_only_user_turn( &test, vec![UserInput::Text { + client_id: None, text: "generate a lobster".to_string(), text_elements: Vec::new(), }], @@ -624,6 +632,7 @@ async fn generated_image_is_replayed_for_image_capable_models() -> Result<()> { .submit(read_only_user_turn( &test, vec![UserInput::Text { + client_id: None, text: "describe the generated image".to_string(), text_elements: Vec::new(), }], @@ -726,6 +735,7 @@ async fn model_change_from_generated_image_to_text_preserves_prior_generated_ima .submit(read_only_user_turn( &test, vec![UserInput::Text { + client_id: None, text: "generate a lobster".to_string(), text_elements: Vec::new(), }], @@ -738,6 +748,7 @@ async fn model_change_from_generated_image_to_text_preserves_prior_generated_ima .submit(read_only_user_turn( &test, vec![UserInput::Text { + client_id: None, text: "describe the generated image".to_string(), text_elements: Vec::new(), }], @@ -842,6 +853,7 @@ async fn thread_rollback_after_generated_image_drops_entire_image_turn_history() .submit(read_only_user_turn( &test, vec![UserInput::Text { + client_id: None, text: "generate a lobster".to_string(), text_elements: Vec::new(), }], @@ -862,6 +874,7 @@ async fn thread_rollback_after_generated_image_drops_entire_image_turn_history() .submit(read_only_user_turn( &test, vec![UserInput::Text { + client_id: None, text: "after rollback".to_string(), text_elements: Vec::new(), }], @@ -1012,6 +1025,7 @@ async fn model_switch_to_smaller_model_updates_token_context_window() -> Result< .submit(read_only_user_turn( &test, vec![UserInput::Text { + client_id: None, text: "use larger model".into(), text_elements: Vec::new(), }], @@ -1055,6 +1069,7 @@ async fn model_switch_to_smaller_model_updates_token_context_window() -> Result< .submit(read_only_user_turn( &test, vec![UserInput::Text { + client_id: None, text: "switch to smaller model".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/model_visible_layout.rs b/codex-rs/core/tests/suite/model_visible_layout.rs index e02e1ffaefe..37ce5018510 100644 --- a/codex-rs/core/tests/suite/model_visible_layout.rs +++ b/codex-rs/core/tests/suite/model_visible_layout.rs @@ -119,6 +119,7 @@ async fn snapshot_model_visible_layout_turn_overrides() -> Result<()> { test.codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: "first turn".into(), text_elements: Vec::new(), }], @@ -155,6 +156,7 @@ async fn snapshot_model_visible_layout_turn_overrides() -> Result<()> { test.codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: "second turn with context updates".into(), text_elements: Vec::new(), }], @@ -245,6 +247,7 @@ async fn snapshot_model_visible_layout_cwd_change_does_not_refresh_agents() -> R test.codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: "first turn in agents_one".into(), text_elements: Vec::new(), }], @@ -279,6 +282,7 @@ async fn snapshot_model_visible_layout_cwd_change_does_not_refresh_agents() -> R test.codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: "second turn in agents_two".into(), text_elements: Vec::new(), }], @@ -364,6 +368,7 @@ async fn snapshot_model_visible_layout_resume_with_personality_change() -> Resul .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "seed resume history".into(), text_elements: Vec::new(), }], @@ -405,6 +410,7 @@ async fn snapshot_model_visible_layout_resume_with_personality_change() -> Resul .codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: "resume and change personality".into(), text_elements: Vec::new(), }], @@ -480,6 +486,7 @@ async fn snapshot_model_visible_layout_resume_override_matches_rollout_model() - .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "seed resume history".into(), text_elements: Vec::new(), }], @@ -522,6 +529,7 @@ async fn snapshot_model_visible_layout_resume_override_matches_rollout_model() - .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "first resumed turn after model override".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/models_cache_ttl.rs b/codex-rs/core/tests/suite/models_cache_ttl.rs index fcd90cbaeab..e127b890184 100644 --- a/codex-rs/core/tests/suite/models_cache_ttl.rs +++ b/codex-rs/core/tests/suite/models_cache_ttl.rs @@ -94,6 +94,7 @@ async fn renews_cache_ttl_on_matching_models_etag() -> Result<()> { codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: "hi".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/models_etag_responses.rs b/codex-rs/core/tests/suite/models_etag_responses.rs index cbd73fffe9c..c776a609036 100644 --- a/codex-rs/core/tests/suite/models_etag_responses.rs +++ b/codex-rs/core/tests/suite/models_etag_responses.rs @@ -106,6 +106,7 @@ async fn refresh_models_on_models_etag_mismatch_and_avoid_duplicate_models_fetch codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: "please run a tool".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/otel.rs b/codex-rs/core/tests/suite/otel.rs index 7f5fa260cd6..251112f5f3e 100644 --- a/codex-rs/core/tests/suite/otel.rs +++ b/codex-rs/core/tests/suite/otel.rs @@ -117,6 +117,7 @@ async fn responses_api_emits_api_request_event() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -164,6 +165,7 @@ async fn process_sse_emits_tracing_for_output_item() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -211,6 +213,7 @@ async fn process_sse_emits_failed_event_on_parse_error() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -259,6 +262,7 @@ async fn process_sse_records_failed_event_when_stream_closes_without_completed() .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -327,6 +331,7 @@ async fn process_sse_failed_event_records_response_error_message() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -393,6 +398,7 @@ async fn process_sse_failed_event_logs_parse_error() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -446,6 +452,7 @@ async fn process_sse_failed_event_logs_missing_error() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -508,6 +515,7 @@ async fn process_sse_failed_event_logs_response_completed_parse_error() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -564,6 +572,7 @@ async fn process_sse_emits_completed_telemetry() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -644,6 +653,7 @@ async fn turn_and_completed_response_spans_record_token_usage() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -733,6 +743,7 @@ async fn handle_responses_span_records_response_kind_and_tool_name() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -828,6 +839,7 @@ async fn record_responses_sets_span_fields_for_response_events() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -918,6 +930,7 @@ async fn handle_response_item_records_tool_result_for_custom_tool_call() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -995,6 +1008,7 @@ async fn handle_response_item_records_tool_result_for_function_call() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -1073,6 +1087,7 @@ async fn handle_response_item_records_tool_result_for_shell_command_call() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -1183,6 +1198,7 @@ async fn handle_shell_command_autoapprove_from_config_records_tool_decision() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -1239,6 +1255,7 @@ async fn handle_shell_command_user_approved_records_tool_decision() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "approved".into(), text_elements: Vec::new(), }], @@ -1310,6 +1327,7 @@ async fn handle_shell_command_user_approved_for_session_records_tool_decision() .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "persist".into(), text_elements: Vec::new(), }], @@ -1381,6 +1399,7 @@ async fn handle_sandbox_error_user_approves_retry_records_tool_decision() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "retry".into(), text_elements: Vec::new(), }], @@ -1452,6 +1471,7 @@ async fn handle_shell_command_user_denies_records_tool_decision() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "deny".into(), text_elements: Vec::new(), }], @@ -1523,6 +1543,7 @@ async fn handle_sandbox_error_user_approves_for_session_records_tool_decision() .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "persist".into(), text_elements: Vec::new(), }], @@ -1595,6 +1616,7 @@ async fn handle_sandbox_error_user_denies_records_tool_decision() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "deny".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/pending_input.rs b/codex-rs/core/tests/suite/pending_input.rs index c8eec9bc8ed..e92185bf91b 100644 --- a/codex-rs/core/tests/suite/pending_input.rs +++ b/codex-rs/core/tests/suite/pending_input.rs @@ -98,6 +98,7 @@ async fn submit_user_input(codex: &CodexThread, text: &str) { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: text.to_string(), text_elements: Vec::new(), }], @@ -116,6 +117,7 @@ async fn submit_danger_full_access_user_turn(test: &TestCodex, text: &str) { test.codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: text.to_string(), text_elements: Vec::new(), }], @@ -147,6 +149,7 @@ async fn steer_user_input(codex: &CodexThread, text: &str) { codex .steer_input( vec![UserInput::Text { + client_id: None, text: text.to_string(), text_elements: Vec::new(), }], @@ -289,6 +292,7 @@ async fn injected_user_input_triggers_follow_up_request_with_deltas() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "first prompt".into(), text_elements: Vec::new(), }], @@ -309,6 +313,7 @@ async fn injected_user_input_triggers_follow_up_request_with_deltas() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "second prompt".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/permissions_messages.rs b/codex-rs/core/tests/suite/permissions_messages.rs index 45d2e32549f..c1c08226798 100644 --- a/codex-rs/core/tests/suite/permissions_messages.rs +++ b/codex-rs/core/tests/suite/permissions_messages.rs @@ -53,6 +53,7 @@ async fn permissions_message_sent_once_on_start() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -94,6 +95,7 @@ async fn permissions_message_added_on_override_change() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello 1".into(), text_elements: Vec::new(), }], @@ -118,6 +120,7 @@ async fn permissions_message_added_on_override_change() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello 2".into(), text_elements: Vec::new(), }], @@ -165,6 +168,7 @@ async fn permissions_message_not_added_when_no_change() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello 1".into(), text_elements: Vec::new(), }], @@ -180,6 +184,7 @@ async fn permissions_message_not_added_when_no_change() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello 2".into(), text_elements: Vec::new(), }], @@ -227,6 +232,7 @@ async fn permissions_message_omitted_when_disabled() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello 1".into(), text_elements: Vec::new(), }], @@ -251,6 +257,7 @@ async fn permissions_message_omitted_when_disabled() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello 2".into(), text_elements: Vec::new(), }], @@ -311,6 +318,7 @@ async fn resume_replays_permissions_messages() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello 1".into(), text_elements: Vec::new(), }], @@ -336,6 +344,7 @@ async fn resume_replays_permissions_messages() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello 2".into(), text_elements: Vec::new(), }], @@ -353,6 +362,7 @@ async fn resume_replays_permissions_messages() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "after resume".into(), text_elements: Vec::new(), }], @@ -414,6 +424,7 @@ async fn resume_and_fork_append_permissions_messages() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello 1".into(), text_elements: Vec::new(), }], @@ -439,6 +450,7 @@ async fn resume_and_fork_append_permissions_messages() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello 2".into(), text_elements: Vec::new(), }], @@ -462,6 +474,7 @@ async fn resume_and_fork_append_permissions_messages() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "after resume".into(), text_elements: Vec::new(), }], @@ -499,6 +512,7 @@ async fn resume_and_fork_append_permissions_messages() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "after fork".into(), text_elements: Vec::new(), }], @@ -561,6 +575,7 @@ async fn permissions_message_includes_writable_roots() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/personality.rs b/codex-rs/core/tests/suite/personality.rs index 06781dc11d4..220f0698374 100644 --- a/codex-rs/core/tests/suite/personality.rs +++ b/codex-rs/core/tests/suite/personality.rs @@ -62,6 +62,7 @@ fn read_only_text_turn_with_personality( turn_permission_fields(PermissionProfile::read_only(), test.cwd_path()); Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: text.into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/plugins.rs b/codex-rs/core/tests/suite/plugins.rs index b89b611a83c..fa7efb062b7 100644 --- a/codex-rs/core/tests/suite/plugins.rs +++ b/codex-rs/core/tests/suite/plugins.rs @@ -222,6 +222,7 @@ async fn capability_sections_render_in_developer_message_in_order() -> Result<() .submit(Op::UserInput { environments: None, items: vec![codex_protocol::user_input::UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -302,6 +303,7 @@ async fn explicit_plugin_mentions_inject_plugin_guidance() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![codex_protocol::user_input::UserInput::Mention { + client_id: None, name: "sample".into(), path: format!("plugin://{SAMPLE_PLUGIN_CONFIG_NAME}"), }], @@ -385,6 +387,7 @@ async fn explicit_plugin_mentions_track_plugin_used_analytics() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![codex_protocol::user_input::UserInput::Mention { + client_id: None, name: "sample".into(), path: format!("plugin://{SAMPLE_PLUGIN_CONFIG_NAME}"), }], diff --git a/codex-rs/core/tests/suite/prompt_caching.rs b/codex-rs/core/tests/suite/prompt_caching.rs index 836761961a3..0889d16dafb 100644 --- a/codex-rs/core/tests/suite/prompt_caching.rs +++ b/codex-rs/core/tests/suite/prompt_caching.rs @@ -148,6 +148,7 @@ async fn prompt_tools_are_consistent_across_requests() -> anyhow::Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello 1".into(), text_elements: Vec::new(), }], @@ -163,6 +164,7 @@ async fn prompt_tools_are_consistent_across_requests() -> anyhow::Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello 2".into(), text_elements: Vec::new(), }], @@ -247,6 +249,7 @@ async fn gpt_5_tools_without_apply_patch_append_apply_patch_instructions() -> an .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello 1".into(), text_elements: Vec::new(), }], @@ -262,6 +265,7 @@ async fn gpt_5_tools_without_apply_patch_append_apply_patch_instructions() -> an .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello 2".into(), text_elements: Vec::new(), }], @@ -328,6 +332,7 @@ async fn prefixes_context_and_instructions_once_and_consistently_across_requests .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello 1".into(), text_elements: Vec::new(), }], @@ -343,6 +348,7 @@ async fn prefixes_context_and_instructions_once_and_consistently_across_requests .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello 2".into(), text_elements: Vec::new(), }], @@ -427,6 +433,7 @@ async fn overrides_turn_context_but_keeps_cached_prefix_and_key_constant() -> an .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello 1".into(), text_elements: Vec::new(), }], @@ -466,6 +473,7 @@ async fn overrides_turn_context_but_keeps_cached_prefix_and_key_constant() -> an .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello 2".into(), text_elements: Vec::new(), }], @@ -548,6 +556,7 @@ async fn override_before_first_turn_emits_environment_context() -> anyhow::Resul .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "first message".into(), text_elements: Vec::new(), }], @@ -703,6 +712,7 @@ async fn per_turn_overrides_keep_cached_prefix_and_key_constant() -> anyhow::Res .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello 1".into(), text_elements: Vec::new(), }], @@ -728,6 +738,7 @@ async fn per_turn_overrides_keep_cached_prefix_and_key_constant() -> anyhow::Res codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: "hello 2".into(), text_elements: Vec::new(), }], @@ -843,6 +854,7 @@ async fn send_user_turn_with_no_changes_does_not_send_environment_context() -> a codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: "hello 1".into(), text_elements: Vec::new(), }], @@ -872,6 +884,7 @@ async fn send_user_turn_with_no_changes_does_not_send_environment_context() -> a codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: "hello 2".into(), text_elements: Vec::new(), }], @@ -984,6 +997,7 @@ async fn send_user_turn_with_changes_sends_environment_context() -> anyhow::Resu codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: "hello 1".into(), text_elements: Vec::new(), }], @@ -1015,6 +1029,7 @@ async fn send_user_turn_with_changes_sends_environment_context() -> anyhow::Resu codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: "hello 2".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/prompt_debug_tests.rs b/codex-rs/core/tests/suite/prompt_debug_tests.rs index dc506bc4746..d46d6f4f0ae 100644 --- a/codex-rs/core/tests/suite/prompt_debug_tests.rs +++ b/codex-rs/core/tests/suite/prompt_debug_tests.rs @@ -26,6 +26,7 @@ async fn build_prompt_input_includes_context_and_user_message() -> Result<()> { let input = build_prompt_input( config, vec![UserInput::Text { + client_id: None, text: "hello from debug prompt".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/quota_exceeded.rs b/codex-rs/core/tests/suite/quota_exceeded.rs index 904c116cfbc..0da8d78b0e6 100644 --- a/codex-rs/core/tests/suite/quota_exceeded.rs +++ b/codex-rs/core/tests/suite/quota_exceeded.rs @@ -43,6 +43,7 @@ async fn quota_exceeded_emits_single_error_event() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "quota?".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/realtime_conversation.rs b/codex-rs/core/tests/suite/realtime_conversation.rs index b99333e66c8..25485e8d13c 100644 --- a/codex-rs/core/tests/suite/realtime_conversation.rs +++ b/codex-rs/core/tests/suite/realtime_conversation.rs @@ -2161,6 +2161,7 @@ async fn conversation_user_text_turn_is_sent_to_realtime_when_active() -> Result .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: user_text.to_string(), text_elements: Vec::new(), }], @@ -2297,6 +2298,7 @@ async fn conversation_user_text_turn_is_capped_when_mirrored_to_realtime() -> Re .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: user_text.clone(), text_elements: Vec::new(), }], @@ -3494,6 +3496,7 @@ async fn inbound_handoff_request_steers_active_turn() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "first prompt".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/remote_env.rs b/codex-rs/core/tests/suite/remote_env.rs index 34cd119f66a..58981538de1 100644 --- a/codex-rs/core/tests/suite/remote_env.rs +++ b/codex-rs/core/tests/suite/remote_env.rs @@ -69,6 +69,7 @@ async fn submit_turn_with_approval_and_environments( test.codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: prompt.into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/remote_models.rs b/codex-rs/core/tests/suite/remote_models.rs index b7b16f9c292..c703aad33d4 100644 --- a/codex-rs/core/tests/suite/remote_models.rs +++ b/codex-rs/core/tests/suite/remote_models.rs @@ -154,6 +154,7 @@ async fn remote_models_config_context_window_override_clamps_to_max_context_wind codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: "check context window".into(), text_elements: Vec::new(), }], @@ -222,6 +223,7 @@ async fn remote_models_config_override_above_max_uses_max_context_window() -> Re codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: "check context window".into(), text_elements: Vec::new(), }], @@ -289,6 +291,7 @@ async fn remote_models_use_context_window_when_config_override_is_absent() -> Re codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: "check context window".into(), text_elements: Vec::new(), }], @@ -369,6 +372,7 @@ async fn remote_models_long_model_slug_is_sent_with_high_reasoning() -> Result<( codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: "check model slug".into(), text_elements: Vec::new(), }], @@ -420,6 +424,7 @@ async fn namespaced_model_slug_uses_catalog_metadata_without_fallback_warning() codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: "check namespaced model metadata".into(), text_elements: Vec::new(), }], @@ -574,6 +579,7 @@ async fn remote_models_remote_model_uses_unified_exec() -> Result<()> { codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: "run call".into(), text_elements: Vec::new(), }], @@ -798,6 +804,7 @@ async fn remote_models_apply_remote_base_instructions() -> Result<()> { codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: "hello remote".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/request_compression.rs b/codex-rs/core/tests/suite/request_compression.rs index fe9cb1e5f84..423b73a6d5f 100644 --- a/codex-rs/core/tests/suite/request_compression.rs +++ b/codex-rs/core/tests/suite/request_compression.rs @@ -42,6 +42,7 @@ async fn request_body_is_zstd_compressed_for_codex_backend_when_enabled() -> any .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "compress me".into(), text_elements: Vec::new(), }], @@ -93,6 +94,7 @@ async fn request_body_is_not_compressed_for_api_key_auth_even_when_enabled() -> .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "do not compress".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/request_permissions.rs b/codex-rs/core/tests/suite/request_permissions.rs index a78e4a016eb..5ca975ea5f1 100644 --- a/codex-rs/core/tests/suite/request_permissions.rs +++ b/codex-rs/core/tests/suite/request_permissions.rs @@ -192,6 +192,7 @@ async fn submit_turn( test.codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: prompt.into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/request_permissions_tool.rs b/codex-rs/core/tests/suite/request_permissions_tool.rs index a122cc399b3..dde8d15f978 100644 --- a/codex-rs/core/tests/suite/request_permissions_tool.rs +++ b/codex-rs/core/tests/suite/request_permissions_tool.rs @@ -144,6 +144,7 @@ async fn submit_turn( test.codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: prompt.into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/request_user_input.rs b/codex-rs/core/tests/suite/request_user_input.rs index d09071d72d9..3c8c8183235 100644 --- a/codex-rs/core/tests/suite/request_user_input.rs +++ b/codex-rs/core/tests/suite/request_user_input.rs @@ -138,6 +138,7 @@ async fn request_user_input_round_trip_for_mode(mode: ModeKind) -> anyhow::Resul codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: "please confirm".into(), text_elements: Vec::new(), }], @@ -282,6 +283,7 @@ async fn request_user_input_interrupt_emits_deferred_token_count() -> anyhow::Re codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: "please confirm".into(), text_elements: Vec::new(), }], @@ -387,6 +389,7 @@ where codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: "please confirm".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/responses_api_proxy_headers.rs b/codex-rs/core/tests/suite/responses_api_proxy_headers.rs index 0c401d05fc8..ccd45d008d8 100644 --- a/codex-rs/core/tests/suite/responses_api_proxy_headers.rs +++ b/codex-rs/core/tests/suite/responses_api_proxy_headers.rs @@ -146,6 +146,7 @@ async fn submit_turn_with_timeout(test: &TestCodex, prompt: &str) -> Result<()> test.codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: prompt.into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/resume.rs b/codex-rs/core/tests/suite/resume.rs index 0dc7eaddcbd..a6e610a7770 100644 --- a/codex-rs/core/tests/suite/resume.rs +++ b/codex-rs/core/tests/suite/resume.rs @@ -88,6 +88,7 @@ async fn resume_includes_initial_messages_from_rollout_events() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "Record some messages".into(), text_elements: text_elements.clone(), }], @@ -175,6 +176,7 @@ async fn resume_includes_initial_messages_from_reasoning_events() -> Result<()> .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "Record reasoning messages".into(), text_elements: Vec::new(), }], @@ -266,6 +268,7 @@ async fn resume_switches_models_preserves_base_instructions() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "Record initial instructions".into(), text_elements: Vec::new(), }], @@ -310,6 +313,7 @@ async fn resume_switches_models_preserves_base_instructions() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "Resume with different model".into(), text_elements: Vec::new(), }], @@ -329,6 +333,7 @@ async fn resume_switches_models_preserves_base_instructions() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "Second turn after resume".into(), text_elements: Vec::new(), }], @@ -403,6 +408,7 @@ async fn resume_model_switch_is_not_duplicated_after_pre_turn_override() -> Resu .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "Record initial instructions".into(), text_elements: Vec::new(), }], @@ -442,6 +448,7 @@ async fn resume_model_switch_is_not_duplicated_after_pre_turn_override() -> Resu .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "first turn after override".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/review.rs b/codex-rs/core/tests/suite/review.rs index 9efaf6c2d12..698c0b7579c 100644 --- a/codex-rs/core/tests/suite/review.rs +++ b/codex-rs/core/tests/suite/review.rs @@ -706,6 +706,7 @@ async fn review_history_surfaces_in_parent_session() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: followup.clone(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/rmcp_client.rs b/codex-rs/core/tests/suite/rmcp_client.rs index 2c86cb393ce..5811d985abd 100644 --- a/codex-rs/core/tests/suite/rmcp_client.rs +++ b/codex-rs/core/tests/suite/rmcp_client.rs @@ -125,6 +125,7 @@ fn user_turn_with_permission_profile( turn_permission_fields(permission_profile, cwd.as_path()); Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: text.into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/safety_check_downgrade.rs b/codex-rs/core/tests/suite/safety_check_downgrade.rs index 3d4cb93b9c5..5f9b9a83303 100644 --- a/codex-rs/core/tests/suite/safety_check_downgrade.rs +++ b/codex-rs/core/tests/suite/safety_check_downgrade.rs @@ -39,6 +39,7 @@ fn disabled_text_turn(test: &TestCodex, text: &str) -> Op { turn_permission_fields(PermissionProfile::Disabled, test.cwd_path()); Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: text.to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/search_tool.rs b/codex-rs/core/tests/suite/search_tool.rs index e9ffeb62ef7..f99bf595a83 100644 --- a/codex-rs/core/tests/suite/search_tool.rs +++ b/codex-rs/core/tests/suite/search_tool.rs @@ -447,6 +447,7 @@ async fn tool_search_returns_deferred_tools_without_follow_up_tool_injection() - .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "Find the calendar create tool".to_string(), text_elements: Vec::new(), }], @@ -861,6 +862,7 @@ async fn tool_search_returns_deferred_dynamic_tool_and_routes_follow_up_call() - .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "Use the automation tool".to_string(), text_elements: Vec::new(), }], @@ -1170,6 +1172,7 @@ async fn tool_search_surfaced_mcp_tool_errors_are_returned_to_model() -> Result< .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "Find the rmcp echo tool and call it.".to_string(), text_elements: Vec::new(), }], @@ -1491,6 +1494,7 @@ async fn tool_search_matches_dynamic_tools_by_name_description_namespace_and_sch .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "Search for the dynamic tool".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/shell_snapshot.rs b/codex-rs/core/tests/suite/shell_snapshot.rs index 69db30b21b4..7a6b8bfa71e 100644 --- a/codex-rs/core/tests/suite/shell_snapshot.rs +++ b/codex-rs/core/tests/suite/shell_snapshot.rs @@ -161,6 +161,7 @@ async fn run_snapshot_command_with_options( codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: "run unified exec with shell snapshot".into(), text_elements: Vec::new(), }], @@ -262,6 +263,7 @@ async fn run_shell_command_snapshot_with_options( codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: "run shell_command with shell snapshot".into(), text_elements: Vec::new(), }], @@ -343,6 +345,7 @@ async fn run_tool_turn_on_harness( codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: prompt.into(), text_elements: Vec::new(), }], @@ -587,6 +590,7 @@ async fn shell_command_snapshot_still_intercepts_apply_patch() -> Result<()> { codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: "apply patch via shell_command with snapshot".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/skill_approval.rs b/codex-rs/core/tests/suite/skill_approval.rs index 2335ba64155..fde46b4a6b1 100644 --- a/codex-rs/core/tests/suite/skill_approval.rs +++ b/codex-rs/core/tests/suite/skill_approval.rs @@ -48,6 +48,7 @@ async fn submit_turn_with_policies( test.codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: prompt.to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/skills.rs b/codex-rs/core/tests/suite/skills.rs index e21668fa842..2931b081f37 100644 --- a/codex-rs/core/tests/suite/skills.rs +++ b/codex-rs/core/tests/suite/skills.rs @@ -77,10 +77,12 @@ async fn user_turn_includes_skill_instructions() -> Result<()> { .submit(Op::UserInput { items: vec![ UserInput::Text { + client_id: None, text: "please use $demo".to_string(), text_elements: Vec::new(), }, UserInput::Skill { + client_id: None, name: "demo".to_string(), path: skill_path.clone(), }, diff --git a/codex-rs/core/tests/suite/sqlite_state.rs b/codex-rs/core/tests/suite/sqlite_state.rs index 8cbc46e1b59..260e96ab9d2 100644 --- a/codex-rs/core/tests/suite/sqlite_state.rs +++ b/codex-rs/core/tests/suite/sqlite_state.rs @@ -468,6 +468,7 @@ async fn mcp_call_marks_thread_memory_mode_polluted_when_configured() -> Result< test.codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: "call the rmcp echo tool".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/stream_error_allows_next_turn.rs b/codex-rs/core/tests/suite/stream_error_allows_next_turn.rs index d82692c26c9..56252cf510d 100644 --- a/codex-rs/core/tests/suite/stream_error_allows_next_turn.rs +++ b/codex-rs/core/tests/suite/stream_error_allows_next_turn.rs @@ -96,6 +96,7 @@ async fn continue_after_stream_error() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "first message".into(), text_elements: Vec::new(), }], @@ -119,6 +120,7 @@ async fn continue_after_stream_error() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "follow up".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/stream_no_completed.rs b/codex-rs/core/tests/suite/stream_no_completed.rs index 471c60db5a9..7f549ef852e 100644 --- a/codex-rs/core/tests/suite/stream_no_completed.rs +++ b/codex-rs/core/tests/suite/stream_no_completed.rs @@ -78,6 +78,7 @@ async fn retries_on_early_close() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/subagent_notifications.rs b/codex-rs/core/tests/suite/subagent_notifications.rs index 8891f6ffec8..57c60dfab83 100644 --- a/codex-rs/core/tests/suite/subagent_notifications.rs +++ b/codex-rs/core/tests/suite/subagent_notifications.rs @@ -765,6 +765,7 @@ async fn subagent_stop_replaces_stop_and_skips_internal_subagents() -> Result<() .thread .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: INTERNAL_SUBAGENT_PROMPT.to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/tool_harness.rs b/codex-rs/core/tests/suite/tool_harness.rs index a6f808b9d79..ca06792db76 100644 --- a/codex-rs/core/tests/suite/tool_harness.rs +++ b/codex-rs/core/tests/suite/tool_harness.rs @@ -104,6 +104,7 @@ async fn shell_command_tool_executes_command_and_streams_output() -> anyhow::Res codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: "please run the shell command".into(), text_elements: Vec::new(), }], @@ -186,6 +187,7 @@ async fn update_plan_tool_emits_plan_update_event() -> anyhow::Result<()> { codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: "please update the plan".into(), text_elements: Vec::new(), }], @@ -278,6 +280,7 @@ async fn update_plan_tool_rejects_malformed_payload() -> anyhow::Result<()> { codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: "please update the plan".into(), text_elements: Vec::new(), }], @@ -380,6 +383,7 @@ async fn apply_patch_tool_executes_and_emits_patch_events() -> anyhow::Result<() codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: "please apply a patch".into(), text_elements: Vec::new(), }], @@ -519,6 +523,7 @@ async fn apply_patch_reports_parse_diagnostics() -> anyhow::Result<()> { codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: "please apply a patch".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/tool_parallelism.rs b/codex-rs/core/tests/suite/tool_parallelism.rs index 76bb27c4230..88baf5f7655 100644 --- a/codex-rs/core/tests/suite/tool_parallelism.rs +++ b/codex-rs/core/tests/suite/tool_parallelism.rs @@ -39,6 +39,7 @@ async fn run_turn(test: &TestCodex, prompt: &str) -> anyhow::Result<()> { test.codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: prompt.into(), text_elements: Vec::new(), }], @@ -366,6 +367,7 @@ async fn shell_tools_start_before_response_completed_when_stream_delayed() -> an test.codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: "stream delayed completion".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/truncation.rs b/codex-rs/core/tests/suite/truncation.rs index fe078977ffb..7be0fc56fe2 100644 --- a/codex-rs/core/tests/suite/truncation.rs +++ b/codex-rs/core/tests/suite/truncation.rs @@ -517,6 +517,7 @@ async fn mcp_image_output_preserves_image_and_no_text_summary() -> Result<()> { .codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: "call the rmcp image tool".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/unified_exec.rs b/codex-rs/core/tests/suite/unified_exec.rs index ba754b025f3..52d50eb73f8 100644 --- a/codex-rs/core/tests/suite/unified_exec.rs +++ b/codex-rs/core/tests/suite/unified_exec.rs @@ -194,6 +194,7 @@ async fn submit_unified_exec_turn( test.codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: prompt.into(), text_elements: Vec::new(), }], @@ -286,6 +287,7 @@ async fn unified_exec_intercepts_apply_patch_exec_command() -> Result<()> { codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: "apply patch via unified exec".into(), text_elements: Vec::new(), }], @@ -2142,6 +2144,7 @@ async fn unified_exec_keeps_long_running_session_after_turn_end() -> Result<()> codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: "keep unified exec process after turn end".into(), text_elements: Vec::new(), }], @@ -2246,6 +2249,7 @@ async fn unified_exec_interrupt_preserves_long_running_session() -> Result<()> { codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: "interrupt long-running unified exec".into(), text_elements: Vec::new(), }], @@ -2719,6 +2723,7 @@ async fn unified_exec_runs_under_sandbox() -> Result<()> { codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: "summarize large output".into(), text_elements: Vec::new(), }], @@ -2842,6 +2847,7 @@ async fn unified_exec_enforces_glob_deny_read_policy() -> Result<()> { codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: "read the fixture files".into(), text_elements: Vec::new(), }], @@ -2981,6 +2987,7 @@ async fn unified_exec_python_prompt_under_seatbelt() -> Result<()> { codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: "start python under seatbelt".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/user_notification.rs b/codex-rs/core/tests/suite/user_notification.rs index 054d926b2b8..84c36f5b7c2 100644 --- a/codex-rs/core/tests/suite/user_notification.rs +++ b/codex-rs/core/tests/suite/user_notification.rs @@ -59,6 +59,7 @@ mv "${tmp_path}" "${payload_path}""#, .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: "hello world".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/user_shell_cmd.rs b/codex-rs/core/tests/suite/user_shell_cmd.rs index 3e2263705ab..242849f0d3f 100644 --- a/codex-rs/core/tests/suite/user_shell_cmd.rs +++ b/codex-rs/core/tests/suite/user_shell_cmd.rs @@ -176,6 +176,7 @@ async fn user_shell_command_does_not_replace_active_turn() -> anyhow::Result<()> .codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: "run model shell command".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/view_image.rs b/codex-rs/core/tests/suite/view_image.rs index e6320ba1a72..8ce38eea9ef 100644 --- a/codex-rs/core/tests/suite/view_image.rs +++ b/codex-rs/core/tests/suite/view_image.rs @@ -206,6 +206,7 @@ async fn assert_user_turn_local_image_resizes_to( .submit(disabled_user_turn( &test, vec![UserInput::LocalImage { + client_id: None, path: abs_path.clone(), detail: None, }], @@ -317,6 +318,7 @@ async fn view_image_tool_attaches_local_image() -> anyhow::Result<()> { .submit(disabled_user_turn( &test, vec![UserInput::Text { + client_id: None, text: "please add the screenshot".into(), text_elements: Vec::new(), }], @@ -710,6 +712,7 @@ async fn view_image_tool_can_preserve_original_resolution_when_requested_on_gpt5 .submit(disabled_user_turn( &test, vec![UserInput::Text { + client_id: None, text: "please add the original screenshot".into(), text_elements: Vec::new(), }], @@ -799,6 +802,7 @@ async fn view_image_tool_errors_clearly_for_unsupported_detail_values() -> anyho .submit(disabled_user_turn( &test, vec![UserInput::Text { + client_id: None, text: "please attach the image at low detail".into(), text_elements: Vec::new(), }], @@ -879,6 +883,7 @@ async fn view_image_tool_treats_null_detail_as_omitted() -> anyhow::Result<()> { .submit(disabled_user_turn( &test, vec![UserInput::Text { + client_id: None, text: "please attach the image with a null detail".into(), text_elements: Vec::new(), }], @@ -969,6 +974,7 @@ async fn view_image_tool_resizes_when_model_lacks_original_detail_support() -> a .submit(disabled_user_turn( &test, vec![UserInput::Text { + client_id: None, text: "please add the screenshot".into(), text_elements: Vec::new(), }], @@ -1063,6 +1069,7 @@ async fn view_image_tool_does_not_force_original_resolution_with_capability_only .submit(disabled_user_turn( &test, vec![UserInput::Text { + client_id: None, text: "please add the screenshot".into(), text_elements: Vec::new(), }], @@ -1145,6 +1152,7 @@ async fn view_image_tool_errors_when_path_is_directory() -> anyhow::Result<()> { .submit(disabled_user_turn( &test, vec![UserInput::Text { + client_id: None, text: "please attach the folder".into(), text_elements: Vec::new(), }], @@ -1216,6 +1224,7 @@ async fn view_image_tool_errors_for_non_image_files() -> anyhow::Result<()> { .submit(disabled_user_turn( &test, vec![UserInput::Text { + client_id: None, text: "please use the view_image tool to read the json file".into(), text_elements: Vec::new(), }], @@ -1293,6 +1302,7 @@ async fn view_image_tool_errors_when_file_missing() -> anyhow::Result<()> { .submit(disabled_user_turn( &test, vec![UserInput::Text { + client_id: None, text: "please attach the missing image".into(), text_elements: Vec::new(), }], @@ -1423,6 +1433,7 @@ async fn view_image_tool_returns_unsupported_message_for_text_only_model() -> an .submit(disabled_user_turn( &test, vec![UserInput::Text { + client_id: None, text: "please attach the image".into(), text_elements: Vec::new(), }], @@ -1494,6 +1505,7 @@ async fn replaces_invalid_local_image_after_bad_request() -> anyhow::Result<()> .submit(disabled_user_turn( &test, vec![UserInput::LocalImage { + client_id: None, path: abs_path.clone(), detail: None, }], diff --git a/codex-rs/core/tests/suite/websocket_fallback.rs b/codex-rs/core/tests/suite/websocket_fallback.rs index be33467d6a5..e8233e73ebb 100644 --- a/codex-rs/core/tests/suite/websocket_fallback.rs +++ b/codex-rs/core/tests/suite/websocket_fallback.rs @@ -154,6 +154,7 @@ async fn websocket_fallback_hides_first_websocket_retry_stream_error() -> Result codex .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: "hello".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/window_headers.rs b/codex-rs/core/tests/suite/window_headers.rs index 953b075f656..f462336da4e 100644 --- a/codex-rs/core/tests/suite/window_headers.rs +++ b/codex-rs/core/tests/suite/window_headers.rs @@ -107,6 +107,7 @@ async fn submit_user_turn(codex: &Arc, text: &str) -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: text.to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/debug-client/src/client.rs b/codex-rs/debug-client/src/client.rs index 90bccf1daad..9f2765dddff 100644 --- a/codex-rs/debug-client/src/client.rs +++ b/codex-rs/debug-client/src/client.rs @@ -199,6 +199,7 @@ impl AppServerClient { params: TurnStartParams { thread_id: thread_id.to_string(), input: vec![UserInput::Text { + client_id: None, text, // Debug client sends plain text with no UI markup spans. text_elements: Vec::new(), diff --git a/codex-rs/exec/src/lib.rs b/codex-rs/exec/src/lib.rs index 2df97b5bd74..cd904fdc051 100644 --- a/codex-rs/exec/src/lib.rs +++ b/codex-rs/exec/src/lib.rs @@ -631,9 +631,14 @@ async fn run_exec_session(args: ExecRunArgs) -> anyhow::Result<()> { let mut items: Vec = imgs .into_iter() .chain(args.images.iter().cloned()) - .map(|path| UserInput::LocalImage { path, detail: None }) + .map(|path| UserInput::LocalImage { + client_id: None, + path, + detail: None, + }) .collect(); items.push(UserInput::Text { + client_id: None, text: prompt_text.clone(), // CLI input doesn't track UI element ranges, so none are available here. text_elements: Vec::new(), @@ -651,9 +656,14 @@ async fn run_exec_session(args: ExecRunArgs) -> anyhow::Result<()> { let prompt_text = resolve_root_prompt(root_prompt); let mut items: Vec = imgs .into_iter() - .map(|path| UserInput::LocalImage { path, detail: None }) + .map(|path| UserInput::LocalImage { + client_id: None, + path, + detail: None, + }) .collect(); items.push(UserInput::Text { + client_id: None, text: prompt_text.clone(), // CLI input doesn't track UI element ranges, so none are available here. text_elements: Vec::new(), diff --git a/codex-rs/mcp-server/src/codex_tool_runner.rs b/codex-rs/mcp-server/src/codex_tool_runner.rs index 62615fd16c5..631641333ad 100644 --- a/codex-rs/mcp-server/src/codex_tool_runner.rs +++ b/codex-rs/mcp-server/src/codex_tool_runner.rs @@ -105,6 +105,7 @@ pub async fn run_codex_tool_session( op: Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: initial_prompt.clone(), // MCP tool prompts are plain text with no UI element ranges. text_elements: Vec::new(), @@ -156,6 +157,7 @@ pub async fn run_codex_tool_session_reply( .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { + client_id: None, text: prompt, // MCP tool prompts are plain text with no UI element ranges. text_elements: Vec::new(), diff --git a/codex-rs/memories/write/src/phase2.rs b/codex-rs/memories/write/src/phase2.rs index c78032d9c2a..9e394bc2582 100644 --- a/codex-rs/memories/write/src/phase2.rs +++ b/codex-rs/memories/write/src/phase2.rs @@ -349,6 +349,7 @@ mod agent { pub(super) fn get_prompt(root: &Path) -> Vec { let prompt = build_consolidation_prompt(root); vec![UserInput::Text { + client_id: None, text: prompt, text_elements: vec![], }] diff --git a/codex-rs/otel/tests/suite/otel_export_routing_policy.rs b/codex-rs/otel/tests/suite/otel_export_routing_policy.rs index 582d9792c55..4b5f0da8c27 100644 --- a/codex-rs/otel/tests/suite/otel_export_routing_policy.rs +++ b/codex-rs/otel/tests/suite/otel_export_routing_policy.rs @@ -134,14 +134,17 @@ fn otel_export_routing_policy_routes_user_prompt_log_and_trace_events() { let _root_guard = root_span.enter(); manager.user_prompt(&[ UserInput::Text { + client_id: None, text: "super secret prompt".to_string(), text_elements: Vec::new(), }, UserInput::Image { + client_id: None, image_url: "https://example.com/image.png".to_string(), detail: None, }, UserInput::LocalImage { + client_id: None, path: PathBuf::from("/tmp/secret.png"), detail: None, }, diff --git a/codex-rs/thread-manager-sample/src/main.rs b/codex-rs/thread-manager-sample/src/main.rs index fb407de3e9a..8e3f1c7dbaf 100644 --- a/codex-rs/thread-manager-sample/src/main.rs +++ b/codex-rs/thread-manager-sample/src/main.rs @@ -291,6 +291,7 @@ async fn run_turn(thread: &CodexThread, thread_id: &str, prompt: String) -> anyh thread .submit(Op::UserInput { items: vec![UserInput::Text { + client_id: None, text: prompt, text_elements: Vec::new(), }], diff --git a/codex-rs/tui/src/app/tests.rs b/codex-rs/tui/src/app/tests.rs index d0c39b91ca6..939238e3ecd 100644 --- a/codex-rs/tui/src/app/tests.rs +++ b/codex-rs/tui/src/app/tests.rs @@ -340,6 +340,7 @@ async fn enqueue_primary_thread_session_replays_turns_before_initial_prompt_subm vec![ThreadItem::UserMessage { id: "user-1".to_string(), content: vec![AppServerUserInput::Text { + client_id: None, text: "earlier prompt".to_string(), text_elements: Vec::new(), }], @@ -376,6 +377,7 @@ async fn enqueue_primary_thread_session_replays_turns_before_initial_prompt_subm assert_eq!( submitted_items, Some(vec![UserInput::Text { + client_id: None, text: initial_prompt, text_elements: Vec::new(), }]) @@ -611,6 +613,7 @@ async fn replayed_turn_complete_submits_restored_queued_follow_up() { Op::UserTurn { items, .. } => assert_eq!( items, vec![UserInput::Text { + client_id: None, text: "queued follow-up".to_string(), text_elements: Vec::new(), }] @@ -854,6 +857,7 @@ async fn replay_thread_snapshot_does_not_submit_queue_before_replay_catches_up() Op::UserTurn { items, .. } => assert_eq!( items, vec![UserInput::Text { + client_id: None, text: "queued follow-up".to_string(), text_elements: Vec::new(), }] @@ -911,6 +915,7 @@ async fn replay_thread_snapshot_restores_pending_pastes_for_submit() { Op::UserTurn { items, .. } => assert_eq!( items, vec![UserInput::Text { + client_id: None, text: large, text_elements: Vec::new(), }] @@ -981,6 +986,7 @@ async fn replay_thread_snapshot_restores_collaboration_mode_for_draft_submit() { assert_eq!( items, vec![UserInput::Text { + client_id: None, text: "draft prompt".to_string(), text_elements: Vec::new(), }] @@ -3265,6 +3271,7 @@ async fn side_thread_snapshot_hides_forked_parent_transcript() { vec![ThreadItem::UserMessage { id: "parent-user".to_string(), content: vec![AppServerUserInput::Text { + client_id: None, text: "parent prompt should stay hidden".to_string(), text_elements: Vec::new(), }], @@ -4691,6 +4698,7 @@ async fn replay_thread_snapshot_replays_turn_history_in_order() { items: vec![ThreadItem::UserMessage { id: "user-1".to_string(), content: vec![AppServerUserInput::Text { + client_id: None, text: "first prompt".to_string(), text_elements: Vec::new(), }], @@ -4708,6 +4716,7 @@ async fn replay_thread_snapshot_replays_turn_history_in_order() { ThreadItem::UserMessage { id: "user-2".to_string(), content: vec![AppServerUserInput::Text { + client_id: None, text: "third prompt".to_string(), text_elements: Vec::new(), }], @@ -4855,6 +4864,7 @@ async fn refreshed_snapshot_session_persists_resumed_turns() { vec![ThreadItem::UserMessage { id: "user-1".to_string(), content: vec![AppServerUserInput::Text { + client_id: None, text: "restored prompt".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/tui/src/app/tests/startup.rs b/codex-rs/tui/src/app/tests/startup.rs index 51d6622e161..ddf49ff51ab 100644 --- a/codex-rs/tui/src/app/tests/startup.rs +++ b/codex-rs/tui/src/app/tests/startup.rs @@ -171,6 +171,7 @@ async fn startup_thread_started_submits_queued_startup_input() { Op::UserTurn { items, .. } => assert_eq!( items, vec![UserInput::Text { + client_id: None, text: "queued before startup completes".to_string(), text_elements: Vec::new(), }] diff --git a/codex-rs/tui/src/app_server_session.rs b/codex-rs/tui/src/app_server_session.rs index 79f8e274b67..adb4a43eb8a 100644 --- a/codex-rs/tui/src/app_server_session.rs +++ b/codex-rs/tui/src/app_server_session.rs @@ -2265,6 +2265,7 @@ mod tests { codex_app_server_protocol::ThreadItem::UserMessage { id: "user-1".to_string(), content: vec![codex_app_server_protocol::UserInput::Text { + client_id: None, text: "hello from history".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/tui/src/chatwidget/input_submission.rs b/codex-rs/tui/src/chatwidget/input_submission.rs index 4700ffc0cc6..5b596dcf238 100644 --- a/codex-rs/tui/src/chatwidget/input_submission.rs +++ b/codex-rs/tui/src/chatwidget/input_submission.rs @@ -163,6 +163,7 @@ impl ChatWidget { for image_url in &remote_image_urls { items.push(UserInput::Image { + client_id: None, url: image_url.clone(), detail: None, }); @@ -170,6 +171,7 @@ impl ChatWidget { for image in &local_images { items.push(UserInput::LocalImage { + client_id: None, path: image.path.clone(), detail: None, }); @@ -177,6 +179,7 @@ impl ChatWidget { if !text.is_empty() { items.push(UserInput::Text { + client_id: None, text: text.clone(), text_elements: app_server_text_elements(&text_elements), }); @@ -209,6 +212,7 @@ impl ChatWidget { && selected_skill_paths.insert(skill.path_to_skills_md.clone()) { items.push(UserInput::Skill { + client_id: None, name: skill.name.clone(), path: skill.path_to_skills_md.to_path_buf(), }); @@ -223,6 +227,7 @@ impl ChatWidget { continue; } items.push(UserInput::Skill { + client_id: None, name: skill.name.clone(), path: skill.path_to_skills_md.to_path_buf(), }); @@ -246,6 +251,7 @@ impl ChatWidget { .find(|plugin| plugin.config_name == plugin_config_name) { items.push(UserInput::Mention { + client_id: None, name: plugin.display_name.clone(), path: binding.path.clone(), }); @@ -272,6 +278,7 @@ impl ChatWidget { { selected_app_ids.insert(app_id.to_string()); items.push(UserInput::Mention { + client_id: None, name: app.name.clone(), path: binding.path.clone(), }); @@ -286,6 +293,7 @@ impl ChatWidget { } let app_id = app.id.as_str(); items.push(UserInput::Mention { + client_id: None, name: app.name.clone(), path: format!("app://{app_id}"), }); diff --git a/codex-rs/tui/src/chatwidget/tests/app_server.rs b/codex-rs/tui/src/chatwidget/tests/app_server.rs index c8d45d2a591..63b613d0c18 100644 --- a/codex-rs/tui/src/chatwidget/tests/app_server.rs +++ b/codex-rs/tui/src/chatwidget/tests/app_server.rs @@ -284,6 +284,7 @@ async fn live_app_server_user_message_item_completed_does_not_duplicate_rendered item: AppServerThreadItem::UserMessage { id: "user-1".to_string(), content: vec![AppServerUserInput::Text { + client_id: None, text: "Hi, are you there?".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/tui/src/chatwidget/tests/composer_submission.rs b/codex-rs/tui/src/chatwidget/tests/composer_submission.rs index 23487af437a..67a97f155de 100644 --- a/codex-rs/tui/src/chatwidget/tests/composer_submission.rs +++ b/codex-rs/tui/src/chatwidget/tests/composer_submission.rs @@ -60,6 +60,7 @@ async fn submission_preserves_text_elements_and_local_images() { assert_eq!( items[0], UserInput::LocalImage { + client_id: None, path: local_images[0].clone(), detail: None, } @@ -67,6 +68,7 @@ async fn submission_preserves_text_elements_and_local_images() { assert_eq!( items[1], UserInput::Text { + client_id: None, text: text.clone(), text_elements: text_elements.clone().into_iter().map(Into::into).collect(), } @@ -271,6 +273,7 @@ async fn submission_with_remote_and_local_images_keeps_local_placeholder_numberi assert_eq!( items[0], UserInput::Image { + client_id: None, url: remote_url.clone(), detail: None, } @@ -278,6 +281,7 @@ async fn submission_with_remote_and_local_images_keeps_local_placeholder_numberi assert_eq!( items[1], UserInput::LocalImage { + client_id: None, path: local_images[0].clone(), detail: None, } @@ -285,6 +289,7 @@ async fn submission_with_remote_and_local_images_keeps_local_placeholder_numberi assert_eq!( items[2], UserInput::Text { + client_id: None, text: text.clone(), text_elements: text_elements.clone().into_iter().map(Into::into).collect(), } @@ -358,6 +363,7 @@ async fn enter_with_only_remote_images_submits_user_turn() { assert_eq!( items, vec![UserInput::Image { + client_id: None, url: remote_url.clone(), detail: None, }] @@ -1248,6 +1254,7 @@ async fn submit_user_message_ignores_inaccessible_app_mentions_from_bindings() { assert_eq!( items, vec![UserInput::Text { + client_id: None, text: "$arabica-uae".to_string(), text_elements: Vec::new(), }] @@ -1259,26 +1266,32 @@ fn user_message_display_from_inputs_matches_flattened_user_message_shape() { let local_image = PathBuf::from("/tmp/local.png"); let rendered = ChatWidget::user_message_display_from_inputs(&[ UserInput::Text { + client_id: None, text: "hello ".to_string(), text_elements: vec![TextElement::new((0..5).into(), /*placeholder*/ None).into()], }, UserInput::Image { + client_id: None, url: "https://example.com/remote.png".to_string(), detail: None, }, UserInput::LocalImage { + client_id: None, path: local_image.clone(), detail: None, }, UserInput::Skill { + client_id: None, name: "demo".to_string(), path: PathBuf::from("/tmp/skill/SKILL.md"), }, UserInput::Mention { + client_id: None, name: "repo".to_string(), path: "app://repo".to_string(), }, UserInput::Text { + client_id: None, text: "world".to_string(), text_elements: vec![TextElement::new((0..5).into(), Some("planet".to_string())).into()], }, @@ -1303,6 +1316,7 @@ fn user_message_display_from_inputs_hides_prompt_context() { let raw_message = "# Context from my IDE setup:\n\n## Active file: src/lib.rs\n\n## My request for Codex:\nAsk $figma"; let mention_start = raw_message.find("$figma").expect("mention in raw message"); let rendered = ChatWidget::user_message_display_from_inputs(&[UserInput::Text { + client_id: None, text: raw_message.to_string(), text_elements: vec![ TextElement::new( @@ -1336,10 +1350,12 @@ async fn committed_user_message_with_hidden_prompt_context_renders_local_images( "user-1", vec![ UserInput::Text { + client_id: None, text: raw_message.to_string(), text_elements: Vec::new(), }, UserInput::LocalImage { + client_id: None, path: local_image.clone(), detail: None, }, diff --git a/codex-rs/tui/src/chatwidget/tests/exec_flow.rs b/codex-rs/tui/src/chatwidget/tests/exec_flow.rs index 8dbace43784..b4956fe7303 100644 --- a/codex-rs/tui/src/chatwidget/tests/exec_flow.rs +++ b/codex-rs/tui/src/chatwidget/tests/exec_flow.rs @@ -1035,6 +1035,7 @@ async fn user_message_during_user_shell_command_is_queued_not_steered() { Op::UserTurn { items, .. } => assert_eq!( items, vec![UserInput::Text { + client_id: None, text: "hi".to_string(), text_elements: Vec::new(), }] diff --git a/codex-rs/tui/src/chatwidget/tests/goal_validation.rs b/codex-rs/tui/src/chatwidget/tests/goal_validation.rs index b186cdf7ff6..373977867bb 100644 --- a/codex-rs/tui/src/chatwidget/tests/goal_validation.rs +++ b/codex-rs/tui/src/chatwidget/tests/goal_validation.rs @@ -218,6 +218,7 @@ async fn queued_goal_slash_command_rejects_oversized_objective_and_drains_next_i Op::UserTurn { items, .. } => assert_eq!( items, vec![UserInput::Text { + client_id: None, text: "continue".to_string(), text_elements: Vec::new(), }] diff --git a/codex-rs/tui/src/chatwidget/tests/helpers.rs b/codex-rs/tui/src/chatwidget/tests/helpers.rs index 63487f18b9e..1ef06d5e47c 100644 --- a/codex-rs/tui/src/chatwidget/tests/helpers.rs +++ b/codex-rs/tui/src/chatwidget/tests/helpers.rs @@ -739,6 +739,7 @@ pub(super) fn replay_user_message_text( chat, item_id, vec![AppServerUserInput::Text { + client_id: None, text: text.into(), text_elements: Vec::new(), }], @@ -928,6 +929,7 @@ pub(super) fn complete_user_message(chat: &mut ChatWidget, item_id: &str, text: chat, item_id, vec![UserInput::Text { + client_id: None, text: text.to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/tui/src/chatwidget/tests/history_replay.rs b/codex-rs/tui/src/chatwidget/tests/history_replay.rs index 58f99231f3f..5bcee614a94 100644 --- a/codex-rs/tui/src/chatwidget/tests/history_replay.rs +++ b/codex-rs/tui/src/chatwidget/tests/history_replay.rs @@ -117,10 +117,12 @@ async fn replayed_user_message_preserves_text_elements_and_local_images() { "user-1", vec![ AppServerUserInput::Text { + client_id: None, text: message.clone(), text_elements: text_elements.clone().into_iter().map(Into::into).collect(), }, AppServerUserInput::LocalImage { + client_id: None, path: local_images[0].clone(), detail: None, }, @@ -189,10 +191,12 @@ async fn replayed_user_message_preserves_remote_image_urls() { "user-1", vec![ AppServerUserInput::Text { + client_id: None, text: message.clone(), text_elements: Vec::new(), }, AppServerUserInput::Image { + client_id: None, url: remote_image_urls[0].clone(), detail: None, }, @@ -451,6 +455,7 @@ async fn replayed_user_message_with_only_remote_images_renders_history_cell() { &mut chat, "user-1", vec![AppServerUserInput::Image { + client_id: None, url: remote_image_urls[0].clone(), detail: None, }], @@ -509,6 +514,7 @@ async fn replayed_user_message_with_only_local_images_renders_history_cell() { &mut chat, "user-1", vec![AppServerUserInput::LocalImage { + client_id: None, path: local_images[0].clone(), detail: None, }], diff --git a/codex-rs/tui/src/chatwidget/tests/plan_mode.rs b/codex-rs/tui/src/chatwidget/tests/plan_mode.rs index 5fc2fa2ce92..2c345cd4944 100644 --- a/codex-rs/tui/src/chatwidget/tests/plan_mode.rs +++ b/codex-rs/tui/src/chatwidget/tests/plan_mode.rs @@ -997,6 +997,7 @@ async fn plan_implementation_popup_skips_when_steer_follows_proposed_plan() { Op::UserTurn { items, .. } => assert_eq!( items, vec![UserInput::Text { + client_id: None, text: "Please continue.".to_string(), text_elements: Vec::new(), }] @@ -1039,6 +1040,7 @@ async fn plan_implementation_popup_shows_after_new_plan_follows_steer() { Op::UserTurn { items, .. } => assert_eq!( items, vec![UserInput::Text { + client_id: None, text: "Please revise.".to_string(), text_elements: Vec::new(), }] @@ -1149,6 +1151,7 @@ async fn submit_user_message_queues_while_compaction_turn_is_running() { Op::UserTurn { items, .. } => assert_eq!( items, vec![UserInput::Text { + client_id: None, text: "queued while compacting".to_string(), text_elements: Vec::new(), }] @@ -1191,6 +1194,7 @@ async fn submit_user_message_queues_while_compaction_turn_is_running() { Op::UserTurn { items, .. } => assert_eq!( items, vec![UserInput::Text { + client_id: None, text: "queued while compacting".to_string(), text_elements: Vec::new(), }] @@ -1256,10 +1260,12 @@ async fn submit_user_message_emits_structured_plugin_mentions_from_bindings() { items, vec![ UserInput::Text { + client_id: None, text: "$sample".to_string(), text_elements: Vec::new(), }, UserInput::Mention { + client_id: None, name: "Sample Plugin".to_string(), path: "plugin://sample@test".to_string(), }, @@ -1429,6 +1435,7 @@ async fn plan_slash_command_with_args_submits_prompt_in_plan_mode() { assert_eq!( items[0], UserInput::Text { + client_id: None, text: "build the plan".to_string(), text_elements: Vec::new(), } diff --git a/codex-rs/tui/src/chatwidget/tests/review_mode.rs b/codex-rs/tui/src/chatwidget/tests/review_mode.rs index 48bc341f6bb..c762611372a 100644 --- a/codex-rs/tui/src/chatwidget/tests/review_mode.rs +++ b/codex-rs/tui/src/chatwidget/tests/review_mode.rs @@ -183,6 +183,7 @@ async fn live_app_server_review_prompt_item_is_not_rendered() { item: AppServerThreadItem::UserMessage { id: "review-prompt".to_string(), content: vec![AppServerUserInput::Text { + client_id: None, text: "Review the code changes against the base branch 'main'.".to_string(), text_elements: Vec::new(), }], @@ -213,6 +214,7 @@ async fn steer_rejection_queues_review_follow_up_before_existing_queued_messages Op::UserTurn { items, .. } => assert_eq!( items, vec![UserInput::Text { + client_id: None, text: "review follow-up one".to_string(), text_elements: Vec::new(), }] @@ -223,6 +225,7 @@ async fn steer_rejection_queues_review_follow_up_before_existing_queued_messages Op::UserTurn { items, .. } => assert_eq!( items, vec![UserInput::Text { + client_id: None, text: "review follow-up two".to_string(), text_elements: Vec::new(), }] @@ -263,6 +266,7 @@ async fn steer_rejection_queues_review_follow_up_before_existing_queued_messages Op::UserTurn { items, .. } => assert_eq!( items, vec![UserInput::Text { + client_id: None, text: "review follow-up one\nreview follow-up two".to_string(), text_elements: Vec::new(), }] @@ -276,6 +280,7 @@ async fn steer_rejection_queues_review_follow_up_before_existing_queued_messages Op::UserTurn { items, .. } => assert_eq!( items, vec![UserInput::Text { + client_id: None, text: "queued later".to_string(), text_elements: Vec::new(), }] @@ -596,10 +601,12 @@ async fn item_completed_pops_pending_steer_with_local_image_and_text_elements() "user-1", vec![ UserInput::Image { + client_id: None, url: "data:image/png;base64,placeholder".to_string(), detail: None, }, UserInput::Text { + client_id: None, text, text_elements: Vec::new(), }, @@ -679,6 +686,7 @@ async fn steer_enter_during_final_stream_preserves_follow_up_prompts_in_order() assert_eq!( first_items, vec![UserInput::Text { + client_id: None, text: "first follow-up".to_string(), text_elements: Vec::new(), }] @@ -690,6 +698,7 @@ async fn steer_enter_during_final_stream_preserves_follow_up_prompts_in_order() assert_eq!( second_items, vec![UserInput::Text { + client_id: None, text: "second follow-up".to_string(), text_elements: Vec::new(), }] @@ -743,6 +752,7 @@ async fn manual_interrupt_restores_pending_steers_to_composer() { Op::UserTurn { items, .. } => assert_eq!( items, vec![UserInput::Text { + client_id: None, text: "queued while streaming".to_string(), text_elements: Vec::new(), }] @@ -779,6 +789,7 @@ async fn esc_interrupt_sends_all_pending_steers_immediately_and_keeps_existing_d Op::UserTurn { items, .. } => assert_eq!( items, vec![UserInput::Text { + client_id: None, text: "first pending steer".to_string(), text_elements: Vec::new(), }] @@ -794,6 +805,7 @@ async fn esc_interrupt_sends_all_pending_steers_immediately_and_keeps_existing_d Op::UserTurn { items, .. } => assert_eq!( items, vec![UserInput::Text { + client_id: None, text: "second pending steer".to_string(), text_elements: Vec::new(), }] @@ -817,6 +829,7 @@ async fn esc_interrupt_sends_all_pending_steers_immediately_and_keeps_existing_d Op::UserTurn { items, .. } => assert_eq!( items, vec![UserInput::Text { + client_id: None, text: "first pending steer\nsecond pending steer".to_string(), text_elements: Vec::new(), }] @@ -893,6 +906,7 @@ async fn manual_interrupt_restores_pending_steer_mention_bindings_to_composer() Op::UserTurn { items, .. } => assert_eq!( items, vec![UserInput::Text { + client_id: None, text: "please use $figma".to_string(), text_elements: vec![ TextElement::new((11..17).into(), Some("$figma".to_string())).into() @@ -932,6 +946,7 @@ async fn manual_interrupt_restores_pending_steers_before_queued_messages() { Op::UserTurn { items, .. } => assert_eq!( items, vec![UserInput::Text { + client_id: None, text: "pending steer".to_string(), text_elements: Vec::new(), }] @@ -1425,6 +1440,7 @@ async fn enter_submits_steer_while_review_is_running() { Op::UserTurn { items, .. } => assert_eq!( items, vec![UserInput::Text { + client_id: None, text: "Steer submitted while /review was running.".to_string(), text_elements: Vec::new(), }] diff --git a/codex-rs/tui/src/chatwidget/tests/side.rs b/codex-rs/tui/src/chatwidget/tests/side.rs index 39a0cb9d733..f36c1e4b3ff 100644 --- a/codex-rs/tui/src/chatwidget/tests/side.rs +++ b/codex-rs/tui/src/chatwidget/tests/side.rs @@ -237,6 +237,7 @@ async fn submit_user_message_as_plain_user_turn_does_not_run_shell_commands() { Op::UserTurn { items, .. } => assert_eq!( items, vec![UserInput::Text { + client_id: None, text: "!echo hello".to_string(), text_elements: Vec::new(), }] diff --git a/codex-rs/tui/src/chatwidget/tests/slash_commands.rs b/codex-rs/tui/src/chatwidget/tests/slash_commands.rs index 0346b970698..86520668c34 100644 --- a/codex-rs/tui/src/chatwidget/tests/slash_commands.rs +++ b/codex-rs/tui/src/chatwidget/tests/slash_commands.rs @@ -267,6 +267,7 @@ async fn queued_empty_bang_shell_reports_help_when_dequeued_and_drains_next_inpu Op::UserTurn { items, .. } => assert_eq!( items, vec![UserInput::Text { + client_id: None, text: "hello after help".to_string(), text_elements: Vec::new(), }] @@ -306,6 +307,7 @@ async fn queued_bang_shell_waits_for_user_shell_completion_before_next_input() { Op::UserTurn { items, .. } => assert_eq!( items, vec![UserInput::Text { + client_id: None, text: "hello after shell".to_string(), text_elements: Vec::new(), }] @@ -339,6 +341,7 @@ async fn assert_cancelled_queued_menu_drains_next_input(command: &str, expected_ Op::UserTurn { items, .. } => assert_eq!( items, vec![UserInput::Text { + client_id: None, text: "hello after menu".to_string(), text_elements: Vec::new(), }] @@ -378,6 +381,7 @@ async fn queued_slash_menu_selection_drains_next_input() { Op::UserTurn { items, .. } => assert_eq!( items, vec![UserInput::Text { + client_id: None, text: "hello after selection".to_string(), text_elements: Vec::new(), }] @@ -429,6 +433,7 @@ async fn queued_bare_rename_drains_next_input_after_name_update() { Op::UserTurn { items, .. } => assert_eq!( items, vec![UserInput::Text { + client_id: None, text: "hello after rename".to_string(), text_elements: Vec::new(), }] @@ -464,6 +469,7 @@ async fn queued_inline_rename_does_not_drain_again_before_turn_started() { Op::UserTurn { items, .. } => assert_eq!( items, vec![UserInput::Text { + client_id: None, text: "first after rename".to_string(), text_elements: Vec::new(), }] @@ -512,6 +518,7 @@ async fn queued_inline_rename_does_not_drain_again_before_turn_started() { Op::UserTurn { items, .. } => assert_eq!( items, vec![UserInput::Text { + client_id: None, text: "second after rename".to_string(), text_elements: Vec::new(), }] @@ -2227,6 +2234,7 @@ async fn queued_fast_slash_applies_before_next_queued_message() { } if service_tier == ServiceTier::Fast.request_value() => assert_eq!( items, vec![UserInput::Text { + client_id: None, text: "hello after fast".to_string(), text_elements: Vec::new(), }] diff --git a/codex-rs/tui/src/chatwidget/tests/status_and_layout.rs b/codex-rs/tui/src/chatwidget/tests/status_and_layout.rs index acb1df6c5ab..910408d3fc0 100644 --- a/codex-rs/tui/src/chatwidget/tests/status_and_layout.rs +++ b/codex-rs/tui/src/chatwidget/tests/status_and_layout.rs @@ -1448,6 +1448,7 @@ async fn final_answer_completion_restores_status_indicator_for_pending_steer() { assert_eq!( items, vec![UserInput::Text { + client_id: None, text: "Please summarize the rest more briefly.".to_string(), text_elements: Vec::new(), }] diff --git a/codex-rs/tui/src/chatwidget/user_messages.rs b/codex-rs/tui/src/chatwidget/user_messages.rs index cdcf650cb1a..80a36b5815d 100644 --- a/codex-rs/tui/src/chatwidget/user_messages.rs +++ b/codex-rs/tui/src/chatwidget/user_messages.rs @@ -578,6 +578,7 @@ impl ChatWidget { UserInput::Text { text, text_elements: current_text_elements, + .. } => append_text_with_rebased_elements( &mut message, &mut text_elements, diff --git a/codex-rs/tui/src/ide_context/prompt.rs b/codex-rs/tui/src/ide_context/prompt.rs index 884ebfb646b..43b1668e92e 100644 --- a/codex-rs/tui/src/ide_context/prompt.rs +++ b/codex-rs/tui/src/ide_context/prompt.rs @@ -33,11 +33,13 @@ pub(crate) fn apply_ide_context_to_user_input( let item = std::mem::replace( &mut items[text_index], UserInput::Text { + client_id: None, text: String::new(), text_elements: Vec::new(), }, ); let UserInput::Text { + client_id: None, text, text_elements, } = item @@ -49,6 +51,7 @@ pub(crate) fn apply_ide_context_to_user_input( items.insert( 0, UserInput::Text { + client_id: None, text: prefix, text_elements: Vec::new(), }, @@ -76,6 +79,7 @@ pub(crate) fn extract_prompt_request_with_offset(message: &str) -> (&str, usize) fn prefixed_text_input(prefix: String, text: String, text_elements: Vec) -> UserInput { let prefix_len = prefix.len(); UserInput::Text { + client_id: None, text: format!("{prefix}{text}"), text_elements: text_elements .into_iter() @@ -268,10 +272,12 @@ mod tests { let text = "Ask $figma".to_string(); let mut items = vec![ UserInput::LocalImage { + client_id: None, path: PathBuf::from("/tmp/screenshot.png"), detail: None, }, UserInput::Text { + client_id: None, text, text_elements: vec![TextElement::new( ByteRange { start: 4, end: 10 }, @@ -288,10 +294,12 @@ mod tests { items, vec![ UserInput::LocalImage { + client_id: None, path: PathBuf::from("/tmp/screenshot.png"), detail: None, }, UserInput::Text { + client_id: None, text: format!("{expected_prefix}Ask $figma"), text_elements: vec![TextElement::new( ByteRange { diff --git a/codex-rs/tui/src/resume_picker.rs b/codex-rs/tui/src/resume_picker.rs index 6e8b65decd8..b98a06dac3a 100644 --- a/codex-rs/tui/src/resume_picker.rs +++ b/codex-rs/tui/src/resume_picker.rs @@ -5782,6 +5782,7 @@ session_picker_view = "dense" ThreadItem::UserMessage { id: String::from("user-1"), content: vec![codex_app_server_protocol::UserInput::Text { + client_id: None, text: String::from("hello from user"), text_elements: Vec::new(), }], From 9f610b0ee8e7e55967343a2619c8c42ff4aeff76 Mon Sep 17 00:00:00 2001 From: Alexi Christakis <167903946+alexi-openai@users.noreply.github.com> Date: Wed, 27 May 2026 23:44:56 -0700 Subject: [PATCH 3/6] Use message-level user input client ids --- .../analytics/src/analytics_client_tests.rs | 5 +- codex-rs/analytics/src/client_tests.rs | 1 + .../schema/json/ClientRequest.json | 47 ++--- .../schema/json/ServerNotification.json | 42 +---- .../codex_app_server_protocol.schemas.json | 54 ++---- .../codex_app_server_protocol.v2.schemas.json | 54 ++---- .../json/v2/ItemCompletedNotification.json | 42 +---- .../json/v2/ItemStartedNotification.json | 42 +---- .../schema/json/v2/ReviewStartResponse.json | 42 +---- .../schema/json/v2/ThreadForkResponse.json | 42 +---- .../schema/json/v2/ThreadListResponse.json | 42 +---- .../json/v2/ThreadMetadataUpdateResponse.json | 42 +---- .../schema/json/v2/ThreadReadResponse.json | 42 +---- .../schema/json/v2/ThreadResumeResponse.json | 42 +---- .../json/v2/ThreadRollbackResponse.json | 42 +---- .../schema/json/v2/ThreadStartResponse.json | 42 +---- .../json/v2/ThreadStartedNotification.json | 42 +---- .../json/v2/ThreadUnarchiveResponse.json | 42 +---- .../json/v2/TurnCompletedNotification.json | 42 +---- .../schema/json/v2/TurnStartParams.json | 41 +---- .../schema/json/v2/TurnStartResponse.json | 42 +---- .../json/v2/TurnStartedNotification.json | 42 +---- .../schema/json/v2/TurnSteerParams.json | 41 +---- .../schema/typescript/v2/ThreadItem.ts | 2 +- .../schema/typescript/v2/TurnStartParams.ts | 2 +- .../schema/typescript/v2/TurnSteerParams.ts | 2 +- .../schema/typescript/v2/UserInput.ts | 4 +- .../src/protocol/thread_history.rs | 172 +++++++++++++++--- .../src/protocol/v2/item.rs | 8 +- .../src/protocol/v2/tests.rs | 75 ++++---- .../src/protocol/v2/turn.rs | 97 ++-------- codex-rs/app-server-test-client/src/lib.rs | 12 +- codex-rs/app-server/README.md | 8 +- .../src/message_processor_tracing_tests.rs | 2 +- .../thread_processor_tests.rs | 2 +- .../src/request_processors/turn_processor.rs | 12 +- codex-rs/app-server/tests/suite/v2/account.rs | 8 +- .../app-server/tests/suite/v2/attestation.rs | 2 +- .../tests/suite/v2/client_metadata.rs | 10 +- .../app-server/tests/suite/v2/compaction.rs | 2 +- .../v2/connection_handling_websocket_unix.rs | 2 +- .../tests/suite/v2/dynamic_tools.rs | 8 +- .../tests/suite/v2/external_agent_config.rs | 4 +- .../app-server/tests/suite/v2/hooks_list.rs | 10 +- .../app-server/tests/suite/v2/initialize.rs | 2 +- .../tests/suite/v2/mcp_server_elicitation.rs | 4 +- .../app-server/tests/suite/v2/mcp_tool.rs | 2 +- .../tests/suite/v2/output_schema.rs | 6 +- .../app-server/tests/suite/v2/plan_item.rs | 2 +- .../tests/suite/v2/remote_thread_store.rs | 2 +- .../tests/suite/v2/request_permissions.rs | 2 +- .../tests/suite/v2/request_user_input.rs | 2 +- codex-rs/app-server/tests/suite/v2/review.rs | 8 +- .../tests/suite/v2/safety_check_downgrade.rs | 8 +- .../tests/suite/v2/thread_archive.rs | 6 +- .../app-server/tests/suite/v2/thread_fork.rs | 4 +- .../tests/suite/v2/thread_inject_items.rs | 6 +- .../app-server/tests/suite/v2/thread_list.rs | 4 +- .../app-server/tests/suite/v2/thread_read.rs | 3 +- .../tests/suite/v2/thread_resume.rs | 48 +++-- .../tests/suite/v2/thread_rollback.rs | 6 +- .../tests/suite/v2/thread_settings_update.rs | 3 +- .../tests/suite/v2/thread_shell_command.rs | 2 +- .../tests/suite/v2/thread_status.rs | 4 +- .../tests/suite/v2/thread_unarchive.rs | 2 +- .../tests/suite/v2/thread_unsubscribe.rs | 4 +- .../tests/suite/v2/turn_interrupt.rs | 6 +- .../app-server/tests/suite/v2/turn_start.rs | 83 +++++---- .../tests/suite/v2/turn_start_zsh_fork.rs | 8 +- .../app-server/tests/suite/v2/turn_steer.rs | 64 +++++-- .../app-server/tests/suite/v2/web_search.rs | 2 +- codex-rs/cli/src/main.rs | 7 +- codex-rs/core-skills/src/injection_tests.rs | 16 -- codex-rs/core/src/agent/control_tests.rs | 7 - codex-rs/core/src/codex_delegate.rs | 1 + codex-rs/core/src/codex_delegate_tests.rs | 1 + codex-rs/core/src/codex_thread.rs | 13 ++ codex-rs/core/src/compact.rs | 1 - codex-rs/core/src/event_mapping.rs | 2 - codex-rs/core/src/event_mapping_tests.rs | 7 - codex-rs/core/src/guardian/prompt.rs | 1 - codex-rs/core/src/hook_runtime.rs | 12 +- codex-rs/core/src/plugins/mentions_tests.rs | 7 - codex-rs/core/src/session/handlers.rs | 18 +- codex-rs/core/src/session/input_queue.rs | 5 +- codex-rs/core/src/session/mod.rs | 34 +++- codex-rs/core/src/session/review.rs | 12 +- codex-rs/core/src/session/tests.rs | 158 +++++++++------- codex-rs/core/src/session/turn.rs | 6 +- codex-rs/core/src/tasks/compact.rs | 1 - codex-rs/core/src/tasks/review.rs | 2 +- .../core/src/tools/handlers/agent_jobs.rs | 1 - .../src/tools/handlers/multi_agents_common.rs | 1 - .../src/tools/handlers/multi_agents_tests.rs | 6 - codex-rs/core/tests/common/test_codex.rs | 1 - codex-rs/core/tests/suite/abort_tasks.rs | 5 - .../core/tests/suite/additional_context.rs | 10 - codex-rs/core/tests/suite/apply_patch_cli.rs | 1 - codex-rs/core/tests/suite/approvals.rs | 2 - codex-rs/core/tests/suite/client.rs | 34 ---- .../core/tests/suite/client_websockets.rs | 2 - codex-rs/core/tests/suite/code_mode.rs | 1 - .../tests/suite/collaboration_instructions.rs | 17 -- codex-rs/core/tests/suite/compact.rs | 33 ---- codex-rs/core/tests/suite/compact_remote.rs | 57 ------ .../core/tests/suite/compact_remote_parity.rs | 7 - .../core/tests/suite/compact_resume_fork.rs | 1 - codex-rs/core/tests/suite/exec_policy.rs | 2 - codex-rs/core/tests/suite/fork_thread.rs | 2 - codex-rs/core/tests/suite/hooks.rs | 2 - codex-rs/core/tests/suite/image_rollout.rs | 4 - codex-rs/core/tests/suite/items.rs | 10 - codex-rs/core/tests/suite/json_result.rs | 1 - .../core/tests/suite/mcp_turn_metadata.rs | 1 - codex-rs/core/tests/suite/model_switching.rs | 15 -- .../core/tests/suite/model_visible_layout.rs | 8 - codex-rs/core/tests/suite/models_cache_ttl.rs | 1 - .../core/tests/suite/models_etag_responses.rs | 1 - codex-rs/core/tests/suite/otel.rs | 22 --- codex-rs/core/tests/suite/pending_input.rs | 6 +- .../core/tests/suite/permissions_messages.rs | 15 -- codex-rs/core/tests/suite/personality.rs | 1 - codex-rs/core/tests/suite/plugins.rs | 3 - codex-rs/core/tests/suite/prompt_caching.rs | 15 -- .../core/tests/suite/prompt_debug_tests.rs | 1 - codex-rs/core/tests/suite/quota_exceeded.rs | 1 - .../core/tests/suite/realtime_conversation.rs | 3 - codex-rs/core/tests/suite/remote_env.rs | 1 - codex-rs/core/tests/suite/remote_models.rs | 7 - .../core/tests/suite/request_compression.rs | 2 - .../core/tests/suite/request_permissions.rs | 1 - .../tests/suite/request_permissions_tool.rs | 1 - .../core/tests/suite/request_user_input.rs | 3 - .../suite/responses_api_proxy_headers.rs | 1 - codex-rs/core/tests/suite/resume.rs | 7 - codex-rs/core/tests/suite/review.rs | 1 - codex-rs/core/tests/suite/rmcp_client.rs | 1 - .../tests/suite/safety_check_downgrade.rs | 1 - codex-rs/core/tests/suite/search_tool.rs | 4 - codex-rs/core/tests/suite/shell_snapshot.rs | 4 - codex-rs/core/tests/suite/skill_approval.rs | 1 - codex-rs/core/tests/suite/skills.rs | 2 - codex-rs/core/tests/suite/sqlite_state.rs | 1 - .../suite/stream_error_allows_next_turn.rs | 2 - .../core/tests/suite/stream_no_completed.rs | 1 - .../tests/suite/subagent_notifications.rs | 1 - codex-rs/core/tests/suite/tool_harness.rs | 5 - codex-rs/core/tests/suite/tool_parallelism.rs | 2 - codex-rs/core/tests/suite/truncation.rs | 1 - codex-rs/core/tests/suite/unified_exec.rs | 7 - .../core/tests/suite/user_notification.rs | 1 - codex-rs/core/tests/suite/user_shell_cmd.rs | 1 - codex-rs/core/tests/suite/view_image.rs | 12 -- .../core/tests/suite/websocket_fallback.rs | 1 - codex-rs/core/tests/suite/window_headers.rs | 1 - codex-rs/debug-client/src/client.rs | 2 +- codex-rs/exec/src/lib.rs | 15 +- codex-rs/mcp-server/src/codex_tool_runner.rs | 3 +- codex-rs/mcp-server/src/message_processor.rs | 1 + codex-rs/memories/write/src/phase2.rs | 1 - .../tests/suite/otel_export_routing_policy.rs | 3 - codex-rs/protocol/src/items.rs | 4 + codex-rs/protocol/src/models.rs | 8 - codex-rs/protocol/src/protocol.rs | 7 +- codex-rs/protocol/src/user_input.rs | 15 +- codex-rs/thread-manager-sample/src/main.rs | 1 - codex-rs/tui/src/app/tests.rs | 15 +- codex-rs/tui/src/app/tests/startup.rs | 1 - codex-rs/tui/src/app_server_session.rs | 4 +- .../tui/src/chatwidget/input_submission.rs | 8 - .../tui/src/chatwidget/tests/app_server.rs | 2 +- .../chatwidget/tests/composer_submission.rs | 16 -- .../tui/src/chatwidget/tests/exec_flow.rs | 1 - .../src/chatwidget/tests/goal_validation.rs | 1 - codex-rs/tui/src/chatwidget/tests/helpers.rs | 4 +- .../src/chatwidget/tests/history_replay.rs | 6 - .../tui/src/chatwidget/tests/plan_mode.rs | 7 - .../tui/src/chatwidget/tests/review_mode.rs | 17 +- codex-rs/tui/src/chatwidget/tests/side.rs | 1 - .../src/chatwidget/tests/slash_commands.rs | 8 - .../src/chatwidget/tests/status_and_layout.rs | 1 - codex-rs/tui/src/ide_context/prompt.rs | 8 - codex-rs/tui/src/resume_picker.rs | 2 +- codex-rs/tui/src/resume_picker/transcript.rs | 7 +- 184 files changed, 797 insertions(+), 1701 deletions(-) diff --git a/codex-rs/analytics/src/analytics_client_tests.rs b/codex-rs/analytics/src/analytics_client_tests.rs index 7c76cf3e779..ca6365f5578 100644 --- a/codex-rs/analytics/src/analytics_client_tests.rs +++ b/codex-rs/analytics/src/analytics_client_tests.rs @@ -272,14 +272,13 @@ fn sample_turn_start_request(thread_id: &str, request_id: i64) -> ClientRequest request_id: RequestId::Integer(request_id), params: TurnStartParams { thread_id: thread_id.to_string(), + client_user_message_id: None, input: vec![ UserInput::Text { - client_id: None, text: "hello".to_string(), text_elements: vec![], }, UserInput::Image { - client_id: None, url: "https://example.com/a.png".to_string(), detail: None, }, @@ -395,12 +394,10 @@ fn sample_turn_steer_request( expected_turn_id: expected_turn_id.to_string(), input: vec![ UserInput::Text { - client_id: None, text: "more".to_string(), text_elements: vec![], }, UserInput::LocalImage { - client_id: None, path: "/tmp/a.png".into(), detail: None, }, diff --git a/codex-rs/analytics/src/client_tests.rs b/codex-rs/analytics/src/client_tests.rs index d0f8fc40e28..68e8a136ad4 100644 --- a/codex-rs/analytics/src/client_tests.rs +++ b/codex-rs/analytics/src/client_tests.rs @@ -89,6 +89,7 @@ fn sample_turn_start_request() -> ClientRequest { request_id: RequestId::Integer(1), params: TurnStartParams { thread_id: "thread-1".to_string(), + client_user_message_id: None, input: Vec::new(), ..Default::default() }, diff --git a/codex-rs/app-server-protocol/schema/json/ClientRequest.json b/codex-rs/app-server-protocol/schema/json/ClientRequest.json index ed54a7935a0..fb51ab25a2c 100644 --- a/codex-rs/app-server-protocol/schema/json/ClientRequest.json +++ b/codex-rs/app-server-protocol/schema/json/ClientRequest.json @@ -3985,6 +3985,12 @@ ], "description": "Override where approval requests are routed for review on this turn and subsequent turns." }, + "clientUserMessageId": { + "type": [ + "string", + "null" + ] + }, "cwd": { "description": "Override the working directory for this turn and subsequent turns.", "type": [ @@ -4071,6 +4077,12 @@ }, "TurnSteerParams": { "properties": { + "clientUserMessageId": { + "type": [ + "string", + "null" + ] + }, "expectedTurnId": { "description": "Required active turn id precondition. The request fails when it does not match the currently active turn.", "type": "string" @@ -4096,13 +4108,6 @@ "oneOf": [ { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "text": { "type": "string" }, @@ -4131,13 +4136,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "detail": { "anyOf": [ { @@ -4169,13 +4167,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "detail": { "anyOf": [ { @@ -4207,13 +4198,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "name": { "type": "string" }, @@ -4238,13 +4222,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "name": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/ServerNotification.json b/codex-rs/app-server-protocol/schema/json/ServerNotification.json index 3035187c410..2f62d8497ba 100644 --- a/codex-rs/app-server-protocol/schema/json/ServerNotification.json +++ b/codex-rs/app-server-protocol/schema/json/ServerNotification.json @@ -3561,6 +3561,13 @@ "oneOf": [ { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "content": { "items": { "$ref": "#/definitions/UserInput" @@ -4945,13 +4952,6 @@ "oneOf": [ { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "text": { "type": "string" }, @@ -4980,13 +4980,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "detail": { "anyOf": [ { @@ -5018,13 +5011,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "detail": { "anyOf": [ { @@ -5056,13 +5042,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "name": { "type": "string" }, @@ -5087,13 +5066,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "name": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json index d7ad26e96dd..877b1f9032f 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -15996,6 +15996,13 @@ "oneOf": [ { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "content": { "items": { "$ref": "#/definitions/v2/UserInput" @@ -18413,6 +18420,12 @@ ], "description": "Override where approval requests are routed for review on this turn and subsequent turns." }, + "clientUserMessageId": { + "type": [ + "string", + "null" + ] + }, "cwd": { "description": "Override the working directory for this turn and subsequent turns.", "type": [ @@ -18540,6 +18553,12 @@ "TurnSteerParams": { "$schema": "http://json-schema.org/draft-07/schema#", "properties": { + "clientUserMessageId": { + "type": [ + "string", + "null" + ] + }, "expectedTurnId": { "description": "Required active turn id precondition. The request fails when it does not match the currently active turn.", "type": "string" @@ -18605,13 +18624,6 @@ "oneOf": [ { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "text": { "type": "string" }, @@ -18640,13 +18652,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "detail": { "anyOf": [ { @@ -18678,13 +18683,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "detail": { "anyOf": [ { @@ -18716,13 +18714,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "name": { "type": "string" }, @@ -18747,13 +18738,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "name": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json index 3e48699cb9f..d14d48cce24 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json @@ -13820,6 +13820,13 @@ "oneOf": [ { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "content": { "items": { "$ref": "#/definitions/UserInput" @@ -16237,6 +16244,12 @@ ], "description": "Override where approval requests are routed for review on this turn and subsequent turns." }, + "clientUserMessageId": { + "type": [ + "string", + "null" + ] + }, "cwd": { "description": "Override the working directory for this turn and subsequent turns.", "type": [ @@ -16364,6 +16377,12 @@ "TurnSteerParams": { "$schema": "http://json-schema.org/draft-07/schema#", "properties": { + "clientUserMessageId": { + "type": [ + "string", + "null" + ] + }, "expectedTurnId": { "description": "Required active turn id precondition. The request fails when it does not match the currently active turn.", "type": "string" @@ -16429,13 +16448,6 @@ "oneOf": [ { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "text": { "type": "string" }, @@ -16464,13 +16476,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "detail": { "anyOf": [ { @@ -16502,13 +16507,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "detail": { "anyOf": [ { @@ -16540,13 +16538,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "name": { "type": "string" }, @@ -16571,13 +16562,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "name": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/v2/ItemCompletedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/ItemCompletedNotification.json index c2bd5f96c91..6bff6581e93 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ItemCompletedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ItemCompletedNotification.json @@ -500,6 +500,13 @@ "oneOf": [ { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "content": { "items": { "$ref": "#/definitions/UserInput" @@ -1166,13 +1173,6 @@ "oneOf": [ { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "text": { "type": "string" }, @@ -1201,13 +1201,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "detail": { "anyOf": [ { @@ -1239,13 +1232,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "detail": { "anyOf": [ { @@ -1277,13 +1263,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "name": { "type": "string" }, @@ -1308,13 +1287,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "name": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/v2/ItemStartedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/ItemStartedNotification.json index 98d46cf5fac..7ab80c69cde 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ItemStartedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ItemStartedNotification.json @@ -500,6 +500,13 @@ "oneOf": [ { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "content": { "items": { "$ref": "#/definitions/UserInput" @@ -1166,13 +1173,6 @@ "oneOf": [ { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "text": { "type": "string" }, @@ -1201,13 +1201,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "detail": { "anyOf": [ { @@ -1239,13 +1232,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "detail": { "anyOf": [ { @@ -1277,13 +1263,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "name": { "type": "string" }, @@ -1308,13 +1287,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "name": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/v2/ReviewStartResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ReviewStartResponse.json index ebcb1633b40..e676366feac 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ReviewStartResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ReviewStartResponse.json @@ -644,6 +644,13 @@ "oneOf": [ { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "content": { "items": { "$ref": "#/definitions/UserInput" @@ -1439,13 +1446,6 @@ "oneOf": [ { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "text": { "type": "string" }, @@ -1474,13 +1474,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "detail": { "anyOf": [ { @@ -1512,13 +1505,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "detail": { "anyOf": [ { @@ -1550,13 +1536,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "name": { "type": "string" }, @@ -1581,13 +1560,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "name": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json index cd5c1f59ddd..ac9c2aceece 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json @@ -1121,6 +1121,13 @@ "oneOf": [ { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "content": { "items": { "$ref": "#/definitions/UserInput" @@ -1999,13 +2006,6 @@ "oneOf": [ { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "text": { "type": "string" }, @@ -2034,13 +2034,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "detail": { "anyOf": [ { @@ -2072,13 +2065,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "detail": { "anyOf": [ { @@ -2110,13 +2096,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "name": { "type": "string" }, @@ -2141,13 +2120,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "name": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json index 730547bef9b..2fa4c4df544 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json @@ -936,6 +936,13 @@ "oneOf": [ { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "content": { "items": { "$ref": "#/definitions/UserInput" @@ -1814,13 +1821,6 @@ "oneOf": [ { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "text": { "type": "string" }, @@ -1849,13 +1849,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "detail": { "anyOf": [ { @@ -1887,13 +1880,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "detail": { "anyOf": [ { @@ -1925,13 +1911,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "name": { "type": "string" }, @@ -1956,13 +1935,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "name": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json index 6f7a52f750e..4d4dd75bd87 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json @@ -936,6 +936,13 @@ "oneOf": [ { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "content": { "items": { "$ref": "#/definitions/UserInput" @@ -1814,13 +1821,6 @@ "oneOf": [ { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "text": { "type": "string" }, @@ -1849,13 +1849,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "detail": { "anyOf": [ { @@ -1887,13 +1880,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "detail": { "anyOf": [ { @@ -1925,13 +1911,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "name": { "type": "string" }, @@ -1956,13 +1935,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "name": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json index fe79798fb18..f9b52825989 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json @@ -936,6 +936,13 @@ "oneOf": [ { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "content": { "items": { "$ref": "#/definitions/UserInput" @@ -1814,13 +1821,6 @@ "oneOf": [ { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "text": { "type": "string" }, @@ -1849,13 +1849,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "detail": { "anyOf": [ { @@ -1887,13 +1880,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "detail": { "anyOf": [ { @@ -1925,13 +1911,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "name": { "type": "string" }, @@ -1956,13 +1935,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "name": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json index 259eb450b9a..17a82d3cff8 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json @@ -1121,6 +1121,13 @@ "oneOf": [ { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "content": { "items": { "$ref": "#/definitions/UserInput" @@ -2025,13 +2032,6 @@ "oneOf": [ { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "text": { "type": "string" }, @@ -2060,13 +2060,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "detail": { "anyOf": [ { @@ -2098,13 +2091,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "detail": { "anyOf": [ { @@ -2136,13 +2122,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "name": { "type": "string" }, @@ -2167,13 +2146,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "name": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json index 9d21bebb476..51bd35e1803 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json @@ -936,6 +936,13 @@ "oneOf": [ { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "content": { "items": { "$ref": "#/definitions/UserInput" @@ -1814,13 +1821,6 @@ "oneOf": [ { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "text": { "type": "string" }, @@ -1849,13 +1849,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "detail": { "anyOf": [ { @@ -1887,13 +1880,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "detail": { "anyOf": [ { @@ -1925,13 +1911,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "name": { "type": "string" }, @@ -1956,13 +1935,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "name": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json index 8a52067d125..6488c33e2eb 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json @@ -1121,6 +1121,13 @@ "oneOf": [ { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "content": { "items": { "$ref": "#/definitions/UserInput" @@ -1999,13 +2006,6 @@ "oneOf": [ { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "text": { "type": "string" }, @@ -2034,13 +2034,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "detail": { "anyOf": [ { @@ -2072,13 +2065,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "detail": { "anyOf": [ { @@ -2110,13 +2096,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "name": { "type": "string" }, @@ -2141,13 +2120,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "name": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json index 814ccadeeb5..7207f1a0f95 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json @@ -936,6 +936,13 @@ "oneOf": [ { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "content": { "items": { "$ref": "#/definitions/UserInput" @@ -1814,13 +1821,6 @@ "oneOf": [ { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "text": { "type": "string" }, @@ -1849,13 +1849,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "detail": { "anyOf": [ { @@ -1887,13 +1880,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "detail": { "anyOf": [ { @@ -1925,13 +1911,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "name": { "type": "string" }, @@ -1956,13 +1935,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "name": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json index 79fcc0c701e..7dadfbb7c74 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json @@ -936,6 +936,13 @@ "oneOf": [ { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "content": { "items": { "$ref": "#/definitions/UserInput" @@ -1814,13 +1821,6 @@ "oneOf": [ { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "text": { "type": "string" }, @@ -1849,13 +1849,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "detail": { "anyOf": [ { @@ -1887,13 +1880,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "detail": { "anyOf": [ { @@ -1925,13 +1911,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "name": { "type": "string" }, @@ -1956,13 +1935,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "name": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/v2/TurnCompletedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/TurnCompletedNotification.json index 90d4150f7d3..e59261e252f 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/TurnCompletedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnCompletedNotification.json @@ -644,6 +644,13 @@ "oneOf": [ { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "content": { "items": { "$ref": "#/definitions/UserInput" @@ -1439,13 +1446,6 @@ "oneOf": [ { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "text": { "type": "string" }, @@ -1474,13 +1474,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "detail": { "anyOf": [ { @@ -1512,13 +1505,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "detail": { "anyOf": [ { @@ -1550,13 +1536,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "name": { "type": "string" }, @@ -1581,13 +1560,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "name": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/v2/TurnStartParams.json b/codex-rs/app-server-protocol/schema/json/v2/TurnStartParams.json index 065cd2caca4..f997976aca1 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/TurnStartParams.json +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnStartParams.json @@ -354,13 +354,6 @@ "oneOf": [ { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "text": { "type": "string" }, @@ -389,13 +382,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "detail": { "anyOf": [ { @@ -427,13 +413,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "detail": { "anyOf": [ { @@ -465,13 +444,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "name": { "type": "string" }, @@ -496,13 +468,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "name": { "type": "string" }, @@ -551,6 +516,12 @@ ], "description": "Override where approval requests are routed for review on this turn and subsequent turns." }, + "clientUserMessageId": { + "type": [ + "string", + "null" + ] + }, "cwd": { "description": "Override the working directory for this turn and subsequent turns.", "type": [ diff --git a/codex-rs/app-server-protocol/schema/json/v2/TurnStartResponse.json b/codex-rs/app-server-protocol/schema/json/v2/TurnStartResponse.json index 2877b9e69ca..40bce29ea10 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/TurnStartResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnStartResponse.json @@ -644,6 +644,13 @@ "oneOf": [ { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "content": { "items": { "$ref": "#/definitions/UserInput" @@ -1439,13 +1446,6 @@ "oneOf": [ { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "text": { "type": "string" }, @@ -1474,13 +1474,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "detail": { "anyOf": [ { @@ -1512,13 +1505,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "detail": { "anyOf": [ { @@ -1550,13 +1536,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "name": { "type": "string" }, @@ -1581,13 +1560,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "name": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/v2/TurnStartedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/TurnStartedNotification.json index 3ce4b697d5f..5ad0bbf0a87 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/TurnStartedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnStartedNotification.json @@ -644,6 +644,13 @@ "oneOf": [ { "properties": { + "clientId": { + "default": null, + "type": [ + "string", + "null" + ] + }, "content": { "items": { "$ref": "#/definitions/UserInput" @@ -1439,13 +1446,6 @@ "oneOf": [ { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "text": { "type": "string" }, @@ -1474,13 +1474,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "detail": { "anyOf": [ { @@ -1512,13 +1505,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "detail": { "anyOf": [ { @@ -1550,13 +1536,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "name": { "type": "string" }, @@ -1581,13 +1560,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "name": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/v2/TurnSteerParams.json b/codex-rs/app-server-protocol/schema/json/v2/TurnSteerParams.json index 2ea95e77ac0..63317f13ba7 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/TurnSteerParams.json +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnSteerParams.json @@ -78,13 +78,6 @@ "oneOf": [ { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "text": { "type": "string" }, @@ -113,13 +106,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "detail": { "anyOf": [ { @@ -151,13 +137,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "detail": { "anyOf": [ { @@ -189,13 +168,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "name": { "type": "string" }, @@ -220,13 +192,6 @@ }, { "properties": { - "clientId": { - "default": null, - "type": [ - "string", - "null" - ] - }, "name": { "type": "string" }, @@ -253,6 +218,12 @@ } }, "properties": { + "clientUserMessageId": { + "type": [ + "string", + "null" + ] + }, "expectedTurnId": { "description": "Required active turn id precondition. The request fails when it does not match the currently active turn.", "type": "string" diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadItem.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadItem.ts index 1b655b460ee..78dff0d7657 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadItem.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadItem.ts @@ -23,7 +23,7 @@ import type { PatchApplyStatus } from "./PatchApplyStatus"; import type { UserInput } from "./UserInput"; import type { WebSearchAction } from "./WebSearchAction"; -export type ThreadItem = { "type": "userMessage", id: string, content: Array, } | { "type": "hookPrompt", id: string, fragments: Array, } | { "type": "agentMessage", id: string, text: string, phase: MessagePhase | null, memoryCitation: MemoryCitation | null, } | { "type": "plan", id: string, text: string, } | { "type": "reasoning", id: string, summary: Array, content: Array, } | { "type": "commandExecution", id: string, +export type ThreadItem = { "type": "userMessage", id: string, clientId: string | null, content: Array, } | { "type": "hookPrompt", id: string, fragments: Array, } | { "type": "agentMessage", id: string, text: string, phase: MessagePhase | null, memoryCitation: MemoryCitation | null, } | { "type": "plan", id: string, text: string, } | { "type": "reasoning", id: string, summary: Array, content: Array, } | { "type": "commandExecution", id: string, /** * The command to be executed. */ diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/TurnStartParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/TurnStartParams.ts index b04919d86b6..afe1ac6d948 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/TurnStartParams.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/TurnStartParams.ts @@ -10,7 +10,7 @@ import type { AskForApproval } from "./AskForApproval"; import type { SandboxPolicy } from "./SandboxPolicy"; import type { UserInput } from "./UserInput"; -export type TurnStartParams = {threadId: string, input: Array, /** +export type TurnStartParams = {threadId: string, clientUserMessageId?: string | null, input: Array, /** * Override the working directory for this turn and subsequent turns. */ cwd?: string | null, /** diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/TurnSteerParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/TurnSteerParams.ts index dae166b4013..a984f2cba87 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/TurnSteerParams.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/TurnSteerParams.ts @@ -3,7 +3,7 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { UserInput } from "./UserInput"; -export type TurnSteerParams = {threadId: string, input: Array, /** +export type TurnSteerParams = {threadId: string, clientUserMessageId?: string | null, input: Array, /** * Required active turn id precondition. The request fails when it does not * match the currently active turn. */ diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/UserInput.ts b/codex-rs/app-server-protocol/schema/typescript/v2/UserInput.ts index b0ecac337a5..2ac37c5228a 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/UserInput.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/UserInput.ts @@ -4,8 +4,8 @@ import type { ImageDetail } from "../ImageDetail"; import type { TextElement } from "./TextElement"; -export type UserInput = { "type": "text", clientId?: string, text: string, +export type UserInput = { "type": "text", text: string, /** * UI-defined spans within `text` used to render or persist special elements. */ -text_elements: Array, } | { "type": "image", clientId?: string, detail?: ImageDetail, url: string, } | { "type": "localImage", clientId?: string, detail?: ImageDetail, path: string, } | { "type": "skill", clientId?: string, name: string, path: string, } | { "type": "mention", clientId?: string, name: string, path: string, }; +text_elements: Array, } | { "type": "image", detail?: ImageDetail, url: string, } | { "type": "localImage", detail?: ImageDetail, path: string, } | { "type": "skill", name: string, path: string, } | { "type": "mention", name: string, path: string, }; diff --git a/codex-rs/app-server-protocol/src/protocol/thread_history.rs b/codex-rs/app-server-protocol/src/protocol/thread_history.rs index b424e4a8659..66b839ee88b 100644 --- a/codex-rs/app-server-protocol/src/protocol/thread_history.rs +++ b/codex-rs/app-server-protocol/src/protocol/thread_history.rs @@ -278,7 +278,11 @@ impl ThreadHistoryBuilder { .unwrap_or_else(|| self.new_turn(/*id*/ None)); let id = self.next_item_id(); let content = self.build_user_inputs(payload); - turn.items.push(ThreadItem::UserMessage { id, content }); + turn.items.push(ThreadItem::UserMessage { + id, + client_id: None, + content, + }); self.current_turn = Some(turn); } @@ -352,8 +356,10 @@ impl ThreadHistoryBuilder { ThreadItem::from(payload.item.clone()), ); } - codex_protocol::items::TurnItem::UserMessage(_) - | codex_protocol::items::TurnItem::HookPrompt(_) + codex_protocol::items::TurnItem::UserMessage(user_message) => { + self.merge_user_message_client_id(&payload.turn_id, user_message); + } + codex_protocol::items::TurnItem::HookPrompt(_) | codex_protocol::items::TurnItem::AgentMessage(_) | codex_protocol::items::TurnItem::Reasoning(_) | codex_protocol::items::TurnItem::WebSearch(_) @@ -376,8 +382,10 @@ impl ThreadHistoryBuilder { ThreadItem::from(payload.item.clone()), ); } - codex_protocol::items::TurnItem::UserMessage(_) - | codex_protocol::items::TurnItem::HookPrompt(_) + codex_protocol::items::TurnItem::UserMessage(user_message) => { + self.merge_user_message_client_id(&payload.turn_id, user_message); + } + codex_protocol::items::TurnItem::HookPrompt(_) | codex_protocol::items::TurnItem::AgentMessage(_) | codex_protocol::items::TurnItem::Reasoning(_) | codex_protocol::items::TurnItem::WebSearch(_) @@ -1059,6 +1067,42 @@ impl ThreadHistoryBuilder { upsert_turn_item(&mut turn.items, item); } + fn merge_user_message_client_id( + &mut self, + turn_id: &str, + user_message: &codex_protocol::items::UserMessageItem, + ) { + let Some(client_id) = user_message.client_id.as_deref() else { + return; + }; + let content = user_message + .content + .iter() + .cloned() + .map(UserInput::from) + .collect::>(); + + if let Some(turn) = self.current_turn.as_mut() + && turn.id == turn_id + && set_user_message_client_id(&mut turn.items, &content, client_id) + { + return; + } + + if let Some(turn) = self.turns.iter_mut().find(|turn| turn.id == turn_id) + && set_user_message_client_id(&mut turn.items, &content, client_id) + { + return; + } + + self.upsert_item_in_turn_id( + turn_id, + ThreadItem::from(codex_protocol::items::TurnItem::UserMessage( + user_message.clone(), + )), + ); + } + fn next_item_id(&mut self) -> String { let id = format!("item-{}", self.next_item_index); self.next_item_index += 1; @@ -1069,7 +1113,6 @@ impl ThreadHistoryBuilder { let mut content = Vec::new(); if !payload.message.trim().is_empty() { content.push(UserInput::Text { - client_id: None, text: payload.message.clone(), text_elements: payload .text_elements @@ -1082,7 +1125,6 @@ impl ThreadHistoryBuilder { if let Some(images) = &payload.images { for (idx, image) in images.iter().enumerate() { content.push(UserInput::Image { - client_id: None, url: image.clone(), detail: payload.image_details.get(idx).copied().flatten(), }); @@ -1090,7 +1132,6 @@ impl ThreadHistoryBuilder { } for (idx, path) in payload.local_images.iter().enumerate() { content.push(UserInput::LocalImage { - client_id: None, path: path.clone(), detail: payload.local_image_details.get(idx).copied().flatten(), }); @@ -1138,6 +1179,27 @@ fn upsert_turn_item(items: &mut Vec, item: ThreadItem) { items.push(item); } +fn set_user_message_client_id( + items: &mut [ThreadItem], + content: &[UserInput], + client_id: &str, +) -> bool { + for item in items.iter_mut().rev() { + if let ThreadItem::UserMessage { + client_id: existing_client_id, + content: existing_content, + .. + } = item + && existing_client_id.is_none() + && existing_content == content + { + *existing_client_id = Some(client_id.to_string()); + return true; + } + } + false +} + struct PendingTurn { id: String, items: Vec, @@ -1295,14 +1357,13 @@ mod tests { first.items[0], ThreadItem::UserMessage { id: "item-1".into(), + client_id: None, content: vec![ UserInput::Text { - client_id: None, text: "First turn".into(), text_elements: Vec::new(), }, UserInput::Image { - client_id: None, url: "https://example.com/one.png".into(), detail: None, } @@ -1335,8 +1396,8 @@ mod tests { second.items[0], ThreadItem::UserMessage { id: "item-4".into(), + client_id: None, content: vec![UserInput::Text { - client_id: None, text: "Second turn".into(), text_elements: Vec::new(), }], @@ -1374,19 +1435,17 @@ mod tests { turns[0].items[0], ThreadItem::UserMessage { id: "item-1".into(), + client_id: None, content: vec![ UserInput::Text { - client_id: None, text: "inspect these".into(), text_elements: Vec::new(), }, UserInput::Image { - client_id: None, url: "https://example.com/image.png".into(), detail: Some(ImageDetail::Original), }, UserInput::LocalImage { - client_id: None, path: local_path, detail: Some(ImageDetail::Original), }, @@ -1419,6 +1478,7 @@ mod tests { turn_id: turn_id.to_string(), item: CoreTurnItem::UserMessage(CoreUserMessageItem { id: "user-item-id".to_string(), + client_id: None, content: Vec::new(), }), started_at_ms: 0, @@ -1443,8 +1503,8 @@ mod tests { turns[0].items[0], ThreadItem::UserMessage { id: "item-1".into(), + client_id: None, content: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -1452,6 +1512,66 @@ mod tests { ); } + #[test] + fn merges_user_message_client_id_from_lifecycle_events() { + let turn_id = "turn-1"; + let thread_id = ThreadId::new(); + let events = vec![ + EventMsg::TurnStarted(TurnStartedEvent { + turn_id: turn_id.to_string(), + trace_id: None, + started_at: None, + model_context_window: None, + collaboration_mode_kind: Default::default(), + }), + EventMsg::UserMessage(UserMessageEvent { + message: "hello".into(), + images: None, + text_elements: Vec::new(), + local_images: Vec::new(), + ..Default::default() + }), + EventMsg::ItemStarted(ItemStartedEvent { + thread_id, + turn_id: turn_id.to_string(), + item: CoreTurnItem::UserMessage(CoreUserMessageItem { + id: "user-item-id".to_string(), + client_id: Some("client-message-1".to_string()), + content: vec![codex_protocol::user_input::UserInput::Text { + text: "hello".into(), + text_elements: Vec::new(), + }], + }), + started_at_ms: 0, + }), + EventMsg::TurnComplete(TurnCompleteEvent { + turn_id: turn_id.to_string(), + last_agent_message: None, + completed_at: None, + duration_ms: None, + time_to_first_token_ms: None, + }), + ]; + + let items = events + .into_iter() + .map(RolloutItem::EventMsg) + .collect::>(); + let turns = build_turns_from_rollout_items(&items); + assert_eq!(turns.len(), 1); + assert_eq!( + turns[0].items, + vec![ThreadItem::UserMessage { + id: "item-1".into(), + client_id: Some("client-message-1".to_string()), + content: vec![UserInput::Text { + text: "hello".into(), + text_elements: Vec::new(), + }], + }] + ); + } + #[test] fn preserves_agent_message_phase_in_history() { let events = vec![EventMsg::AgentMessage(AgentMessageEvent { @@ -1525,8 +1645,8 @@ mod tests { items: vec![ ThreadItem::UserMessage { id: "item-1".into(), + client_id: None, content: vec![UserInput::Text { - client_id: None, text: "generate an image".into(), text_elements: Vec::new(), }], @@ -1645,8 +1765,8 @@ mod tests { first_turn.items[0], ThreadItem::UserMessage { id: "item-1".into(), + client_id: None, content: vec![UserInput::Text { - client_id: None, text: "Please do the thing".into(), text_elements: Vec::new(), }], @@ -1669,8 +1789,8 @@ mod tests { second_turn.items[0], ThreadItem::UserMessage { id: "item-3".into(), + client_id: None, content: vec![UserInput::Text { - client_id: None, text: "Let's try again".into(), text_elements: Vec::new(), }], @@ -1745,8 +1865,8 @@ mod tests { vec![ ThreadItem::UserMessage { id: "item-1".into(), + client_id: None, content: vec![UserInput::Text { - client_id: None, text: "First".into(), text_elements: Vec::new(), }], @@ -1764,8 +1884,8 @@ mod tests { vec![ ThreadItem::UserMessage { id: "item-3".into(), + client_id: None, content: vec![UserInput::Text { - client_id: None, text: "Third".into(), text_elements: Vec::new(), }], @@ -1863,16 +1983,16 @@ mod tests { vec![ ThreadItem::UserMessage { id: "item-1".into(), + client_id: None, content: vec![UserInput::Text { - client_id: None, text: "Start".into(), text_elements: Vec::new(), }], }, ThreadItem::UserMessage { id: "item-2".into(), + client_id: None, content: vec![UserInput::Text { - client_id: None, text: "Steer".into(), text_elements: Vec::new(), }], @@ -2545,8 +2665,8 @@ mod tests { turns[1].items[0], ThreadItem::UserMessage { id: "item-2".into(), + client_id: None, content: vec![UserInput::Text { - client_id: None, text: "second".into(), text_elements: Vec::new(), }], @@ -2602,8 +2722,8 @@ mod tests { vec![ ThreadItem::UserMessage { id: "item-1".into(), + client_id: None, content: vec![UserInput::Text { - client_id: None, text: "apply patch".into(), text_elements: Vec::new(), }], @@ -2671,8 +2791,8 @@ mod tests { vec![ ThreadItem::UserMessage { id: "item-1".into(), + client_id: None, content: vec![UserInput::Text { - client_id: None, text: "apply patch".into(), text_elements: Vec::new(), }], @@ -3125,8 +3245,8 @@ mod tests { items_view: TurnItemsView::Full, items: vec![ThreadItem::UserMessage { id: "item-1".into(), + client_id: None, content: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server-protocol/src/protocol/v2/item.rs b/codex-rs/app-server-protocol/src/protocol/v2/item.rs index 39e489d6aa8..1e775836a6b 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2/item.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2/item.rs @@ -212,7 +212,12 @@ impl CommandAction { pub enum ThreadItem { #[serde(rename_all = "camelCase")] #[ts(rename_all = "camelCase")] - UserMessage { id: String, content: Vec }, + UserMessage { + id: String, + #[serde(default)] + client_id: Option, + content: Vec, + }, #[serde(rename_all = "camelCase")] #[ts(rename_all = "camelCase")] HookPrompt { @@ -776,6 +781,7 @@ impl From for ThreadItem { match value { CoreTurnItem::UserMessage(user) => ThreadItem::UserMessage { id: user.id, + client_id: user.client_id, content: user.content.into_iter().map(UserInput::from).collect(), }, CoreTurnItem::HookPrompt(hook_prompt) => ThreadItem::HookPrompt { diff --git a/codex-rs/app-server-protocol/src/protocol/v2/tests.rs b/codex-rs/app-server-protocol/src/protocol/v2/tests.rs index e49c55be126..e50018259ce 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2/tests.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2/tests.rs @@ -1756,6 +1756,7 @@ fn client_request_turn_start_granular_approval_policy_is_marked_experimental() { request_id: crate::RequestId::Integer(4), params: TurnStartParams { thread_id: "thr_123".to_string(), + client_user_message_id: None, input: Vec::new(), approval_policy: Some(AskForApproval::Granular { sandbox_approval: false, @@ -2314,29 +2315,25 @@ fn network_requirements_serializes_canonical_and_legacy_fields() { fn core_turn_item_into_thread_item_converts_supported_variants() { let user_item = TurnItem::UserMessage(UserMessageItem { id: "user-1".to_string(), + client_id: Some("client-message-1".to_string()), content: vec![ CoreUserInput::Text { - client_id: Some("client-text-1".to_string()), text: "hello".to_string(), text_elements: Vec::new(), }, CoreUserInput::Image { - client_id: Some("client-image-1".to_string()), image_url: "https://example.com/image.png".to_string(), detail: Some(ImageDetail::Original), }, CoreUserInput::LocalImage { - client_id: Some("client-local-image-1".to_string()), path: PathBuf::from("local/image.png"), detail: Some(ImageDetail::Original), }, CoreUserInput::Skill { - client_id: Some("client-skill-1".to_string()), name: "skill-creator".to_string(), path: PathBuf::from("/repo/.codex/skills/skill-creator/SKILL.md"), }, CoreUserInput::Mention { - client_id: Some("client-mention-1".to_string()), name: "Demo App".to_string(), path: "app://demo-app".to_string(), }, @@ -2347,29 +2344,25 @@ fn core_turn_item_into_thread_item_converts_supported_variants() { ThreadItem::from(user_item), ThreadItem::UserMessage { id: "user-1".to_string(), + client_id: Some("client-message-1".to_string()), content: vec![ UserInput::Text { - client_id: Some("client-text-1".to_string()), text: "hello".to_string(), text_elements: Vec::new(), }, UserInput::Image { - client_id: Some("client-image-1".to_string()), url: "https://example.com/image.png".to_string(), detail: Some(ImageDetail::Original), }, UserInput::LocalImage { - client_id: Some("client-local-image-1".to_string()), path: PathBuf::from("local/image.png"), detail: Some(ImageDetail::Original), }, UserInput::Skill { - client_id: Some("client-skill-1".to_string()), name: "skill-creator".to_string(), path: PathBuf::from("/repo/.codex/skills/skill-creator/SKILL.md"), }, UserInput::Mention { - client_id: Some("client-mention-1".to_string()), name: "Demo App".to_string(), path: "app://demo-app".to_string(), }, @@ -2583,51 +2576,58 @@ fn core_turn_item_into_thread_item_converts_supported_variants() { } #[test] -fn user_input_client_id_uses_camel_case_wire_field() { - let input = UserInput::Text { - client_id: Some("client-text-1".to_string()), - text: "hello".to_string(), - text_elements: Vec::new(), +fn user_message_client_id_uses_camel_case_wire_field() { + let item = ThreadItem::UserMessage { + id: "user-1".to_string(), + client_id: Some("client-message-1".to_string()), + content: vec![UserInput::Text { + text: "hello".to_string(), + text_elements: Vec::new(), + }], }; - let value = serde_json::to_value(&input).expect("serialize user input"); + let value = serde_json::to_value(&item).expect("serialize user message"); assert_eq!( value, json!({ - "type": "text", - "clientId": "client-text-1", - "text": "hello", - "text_elements": [], + "type": "userMessage", + "id": "user-1", + "clientId": "client-message-1", + "content": [{ + "type": "text", + "text": "hello", + "text_elements": [], + }], }) ); - let decoded = serde_json::from_value::(value).expect("deserialize user input"); - assert_eq!(decoded, input); + let decoded = serde_json::from_value::(value).expect("deserialize user message"); + assert_eq!(decoded, item); - let input_without_client_id = UserInput::Text { + let item_without_client_id = ThreadItem::UserMessage { + id: "user-1".to_string(), client_id: None, - text: "hello".to_string(), - text_elements: Vec::new(), + content: Vec::new(), }; assert_eq!( - serde_json::to_value(&input_without_client_id).expect("serialize user input"), + serde_json::to_value(&item_without_client_id).expect("serialize user message"), json!({ - "type": "text", + "type": "userMessage", + "id": "user-1", "clientId": null, - "text": "hello", - "text_elements": [], + "content": [], }) ); assert_eq!( - serde_json::from_value::(json!({ - "type": "text", - "text": "hello", - "text_elements": [], + serde_json::from_value::(json!({ + "type": "userMessage", + "id": "user-1", + "content": [], })) - .expect("deserialize user input without client id"), - input_without_client_id + .expect("deserialize user message without client id"), + item_without_client_id ); } @@ -2635,13 +2635,11 @@ fn user_input_client_id_uses_camel_case_wire_field() { fn user_input_into_core_preserves_image_detail() { assert_eq!( UserInput::Image { - client_id: Some("client-image-1".to_string()), url: "https://example.com/image.png".to_string(), detail: Some(ImageDetail::Original), } .into_core(), CoreUserInput::Image { - client_id: Some("client-image-1".to_string()), image_url: "https://example.com/image.png".to_string(), detail: Some(ImageDetail::Original), } @@ -2649,13 +2647,11 @@ fn user_input_into_core_preserves_image_detail() { assert_eq!( UserInput::LocalImage { - client_id: Some("client-local-image-1".to_string()), path: PathBuf::from("local/image.png"), detail: Some(ImageDetail::Original), } .into_core(), CoreUserInput::LocalImage { - client_id: Some("client-local-image-1".to_string()), path: PathBuf::from("local/image.png"), detail: Some(ImageDetail::Original), } @@ -3645,6 +3641,7 @@ fn turn_start_params_preserve_explicit_null_service_tier() { let without_override = TurnStartParams { thread_id: "thread_123".to_string(), + client_user_message_id: None, input: vec![], responsesapi_client_metadata: None, additional_context: None, diff --git a/codex-rs/app-server-protocol/src/protocol/v2/turn.rs b/codex-rs/app-server-protocol/src/protocol/v2/turn.rs index b31597bf040..f9bfe21479d 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2/turn.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2/turn.rs @@ -65,6 +65,8 @@ pub struct AdditionalContextEntry { #[ts(export_to = "v2/")] pub struct TurnStartParams { pub thread_id: String, + #[ts(optional = nullable)] + pub client_user_message_id: Option, pub input: Vec, /// Optional turn-scoped Responses API client metadata. #[experimental("turn/start.responsesapiClientMetadata")] @@ -157,6 +159,8 @@ pub struct TurnStartResponse { #[ts(export_to = "v2/")] pub struct TurnSteerParams { pub thread_id: String, + #[ts(optional = nullable)] + pub client_user_message_id: Option, pub input: Vec, /// Optional turn-scoped Responses API client metadata. #[experimental("turn/steer.responsesapiClientMetadata")] @@ -266,43 +270,28 @@ impl From for CoreTextElement { #[ts(export_to = "v2/")] pub enum UserInput { Text { - #[serde(rename = "clientId", default)] - #[ts(rename = "clientId", optional)] - client_id: Option, text: String, /// UI-defined spans within `text` used to render or persist special elements. #[serde(default)] text_elements: Vec, }, Image { - #[serde(rename = "clientId", default)] - #[ts(rename = "clientId", optional)] - client_id: Option, #[serde(default)] #[ts(optional)] detail: Option, url: String, }, LocalImage { - #[serde(rename = "clientId", default)] - #[ts(rename = "clientId", optional)] - client_id: Option, #[serde(default)] #[ts(optional)] detail: Option, path: PathBuf, }, Skill { - #[serde(rename = "clientId", default)] - #[ts(rename = "clientId", optional)] - client_id: Option, name: String, path: PathBuf, }, Mention { - #[serde(rename = "clientId", default)] - #[ts(rename = "clientId", optional)] - client_id: Option, name: String, path: String, }, @@ -312,50 +301,19 @@ impl UserInput { pub fn into_core(self) -> CoreUserInput { match self { UserInput::Text { - client_id, text, text_elements, } => CoreUserInput::Text { - client_id, text, text_elements: text_elements.into_iter().map(Into::into).collect(), }, - UserInput::Image { - client_id, - url, - detail, - } => CoreUserInput::Image { - client_id, + UserInput::Image { url, detail } => CoreUserInput::Image { image_url: url, detail, }, - UserInput::LocalImage { - client_id, - path, - detail, - } => CoreUserInput::LocalImage { - client_id, - path, - detail, - }, - UserInput::Skill { - client_id, - name, - path, - } => CoreUserInput::Skill { - client_id, - name, - path, - }, - UserInput::Mention { - client_id, - name, - path, - } => CoreUserInput::Mention { - client_id, - name, - path, - }, + UserInput::LocalImage { path, detail } => CoreUserInput::LocalImage { path, detail }, + UserInput::Skill { name, path } => CoreUserInput::Skill { name, path }, + UserInput::Mention { name, path } => CoreUserInput::Mention { name, path }, } } } @@ -364,50 +322,19 @@ impl From for UserInput { fn from(value: CoreUserInput) -> Self { match value { CoreUserInput::Text { - client_id, text, text_elements, } => UserInput::Text { - client_id, text, text_elements: text_elements.into_iter().map(Into::into).collect(), }, - CoreUserInput::Image { - client_id, - image_url, - detail, - } => UserInput::Image { - client_id, + CoreUserInput::Image { image_url, detail } => UserInput::Image { url: image_url, detail, }, - CoreUserInput::LocalImage { - client_id, - path, - detail, - } => UserInput::LocalImage { - client_id, - path, - detail, - }, - CoreUserInput::Skill { - client_id, - name, - path, - } => UserInput::Skill { - client_id, - name, - path, - }, - CoreUserInput::Mention { - client_id, - name, - path, - } => UserInput::Mention { - client_id, - name, - path, - }, + CoreUserInput::LocalImage { path, detail } => UserInput::LocalImage { path, detail }, + CoreUserInput::Skill { name, path } => UserInput::Skill { name, path }, + CoreUserInput::Mention { name, path } => UserInput::Mention { name, path }, _ => unreachable!("unsupported user input variant"), } } diff --git a/codex-rs/app-server-test-client/src/lib.rs b/codex-rs/app-server-test-client/src/lib.rs index 599edfca90c..e89a8dbe99b 100644 --- a/codex-rs/app-server-test-client/src/lib.rs +++ b/codex-rs/app-server-test-client/src/lib.rs @@ -734,8 +734,8 @@ async fn trigger_zsh_fork_multi_cmd_approval( let mut turn_params = TurnStartParams { thread_id: thread_response.thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: message, text_elements: Vec::new(), }], @@ -819,8 +819,8 @@ async fn resume_message_v2( let turn_response = client.turn_start(TurnStartParams { thread_id: resume_response.thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: user_message, text_elements: Vec::new(), }], @@ -961,8 +961,8 @@ async fn send_message_v2_with_policies( println!("< thread/start response: {thread_response:?}"); let mut turn_params = TurnStartParams { thread_id: thread_response.thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: user_message, // Test client sends plain text without UI element ranges. text_elements: Vec::new(), @@ -1002,8 +1002,8 @@ async fn send_follow_up_v2( let first_turn_params = TurnStartParams { thread_id: thread_response.thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: first_message, // Test client sends plain text without UI element ranges. text_elements: Vec::new(), @@ -1016,8 +1016,8 @@ async fn send_follow_up_v2( let follow_up_params = TurnStartParams { thread_id: thread_response.thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: follow_up_message, // Test client sends plain text without UI element ranges. text_elements: Vec::new(), @@ -1260,8 +1260,8 @@ fn live_elicitation_timeout_pause( let started_at = Instant::now(); let turn_response = client.turn_start(TurnStartParams { thread_id: thread_id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: prompt, text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/README.md b/codex-rs/app-server/README.md index b91b206d6b6..9295d94d226 100644 --- a/codex-rs/app-server/README.md +++ b/codex-rs/app-server/README.md @@ -158,9 +158,9 @@ Example with notification opt-out: - `thread/shellCommand` — run a user-initiated `!` shell command against a thread; this runs unsandboxed with full access rather than inheriting the thread sandbox policy. Returns `{}` immediately while progress streams through standard turn/item notifications and any active turn receives the formatted output in its message stream. - `thread/backgroundTerminals/clean` — terminate all running background terminals for a thread (experimental; requires `capabilities.experimentalApi`); returns `{}` when the cleanup request is accepted. - `thread/rollback` — drop the last N turns from the agent’s in-memory context and persist a rollback marker in the rollout so future resumes see the pruned history; returns the updated `thread` (with `turns` populated) on success. -- `turn/start` — add user input to a thread and begin Codex generation; responds with the initial `turn` object and streams `turn/started`, `item/*`, and `turn/completed` notifications. Experimental `runtimeWorkspaceRoots` replaces the thread-scoped runtime workspace roots used to materialize `:workspace_roots`; relative paths resolve against the effective turn cwd. Prefer experimental `permissions` profile selection by id for permission overrides; the legacy `sandboxPolicy` field is still accepted but cannot be combined with `permissions`. For `collaborationMode`, `settings.developer_instructions: null` means "use built-in instructions for the selected mode". +- `turn/start` — add user input to a thread and begin Codex generation; responds with the initial `turn` object and streams `turn/started`, `item/*`, and `turn/completed` notifications. `clientUserMessageId` is optional; when supplied, the corresponding `userMessage` item echoes it as `clientId`. Experimental `runtimeWorkspaceRoots` replaces the thread-scoped runtime workspace roots used to materialize `:workspace_roots`; relative paths resolve against the effective turn cwd. Prefer experimental `permissions` profile selection by id for permission overrides; the legacy `sandboxPolicy` field is still accepted but cannot be combined with `permissions`. For `collaborationMode`, `settings.developer_instructions: null` means "use built-in instructions for the selected mode". - `thread/inject_items` — append raw Responses API items to a loaded thread’s model-visible history without starting a user turn; returns `{}` on success. -- `turn/steer` — add user input to an already in-flight regular turn without starting a new turn; returns the active `turnId` that accepted the input. Review and manual compaction turns reject `turn/steer`. +- `turn/steer` — add user input to an already in-flight regular turn without starting a new turn; returns the active `turnId` that accepted the input. `clientUserMessageId` is optional; when supplied, the corresponding `userMessage` item echoes it as `clientId`. Review and manual compaction turns reject `turn/steer`. - `turn/interrupt` — request cancellation of an in-flight turn by `(thread_id, turn_id)`; success is an empty `{}` response and the turn finishes with `status: "interrupted"`. - `thread/realtime/start` — start a thread-scoped realtime session (experimental); pass `outputModality: "text"` or `outputModality: "audio"` to choose model output, returns `{}` and streams `thread/realtime/*` notifications. Omit `transport` for the websocket transport, or pass `{ "type": "webrtc", "sdp": "..." }` to create a WebRTC session from a browser-generated SDP offer; the remote answer SDP is emitted as `thread/realtime/sdp`. - `thread/realtime/appendAudio` — append an input audio chunk to the active realtime session (experimental); returns `{}`. @@ -662,6 +662,7 @@ You can optionally specify config overrides on the new turn. If specified, these ```json { "method": "turn/start", "id": 30, "params": { "threadId": "thr_123", + "clientUserMessageId": "client_msg_123", "input": [ { "type": "text", "text": "Run tests" } ], // Below are optional config overrides "cwd": "/Users/me/project", @@ -860,6 +861,7 @@ not emit `turn/started` and does not accept thread settings overrides. ```json { "method": "turn/steer", "id": 32, "params": { "threadId": "thr_123", + "clientUserMessageId": "client_msg_124", "input": [ { "type": "text", "text": "Actually focus on failing tests first." } ], "expectedTurnId": "turn_456" } } @@ -1245,7 +1247,7 @@ Today both notifications carry an empty `items` array even when item events were `ThreadItem` is the tagged union carried in turn responses and `item/*` notifications. Currently we support events for the following items: -- `userMessage` — `{id, content}` where `content` is a list of user inputs (`text`, `image`, or `localImage`). +- `userMessage` — `{id, clientId, content}` where `clientId` is the optional `clientUserMessageId` supplied to `turn/start` or `turn/steer`, and `content` is a list of user inputs (`text`, `image`, or `localImage`). - `agentMessage` — `{id, text}` containing the accumulated agent reply. - `plan` — `{id, text}` emitted for plan-mode turns; plan text can stream via `item/plan/delta` (experimental). - `reasoning` — `{id, summary, content}` where `summary` holds streamed reasoning summaries (applicable for most OpenAI models) and `content` holds raw reasoning blocks (applicable for e.g. open source models). diff --git a/codex-rs/app-server/src/message_processor_tracing_tests.rs b/codex-rs/app-server/src/message_processor_tracing_tests.rs index 0702596cc50..94b5a1af27d 100644 --- a/codex-rs/app-server/src/message_processor_tracing_tests.rs +++ b/codex-rs/app-server/src/message_processor_tracing_tests.rs @@ -653,8 +653,8 @@ async fn turn_start_jsonrpc_span_parents_core_turn_spans() -> Result<()> { params: TurnStartParams { environments: None, thread_id, + client_user_message_id: None, input: vec![UserInput::Text { - client_id: None, text: "hello".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/src/request_processors/thread_processor_tests.rs b/codex-rs/app-server/src/request_processors/thread_processor_tests.rs index e2fbbefacee..ebcc3544800 100644 --- a/codex-rs/app-server/src/request_processors/thread_processor_tests.rs +++ b/codex-rs/app-server/src/request_processors/thread_processor_tests.rs @@ -224,8 +224,8 @@ 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 { - client_id: None, text: "live".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/src/request_processors/turn_processor.rs b/codex-rs/app-server/src/request_processors/turn_processor.rs index e41628a606a..2fbc64c3987 100644 --- a/codex-rs/app-server/src/request_processors/turn_processor.rs +++ b/codex-rs/app-server/src/request_processors/turn_processor.rs @@ -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(¶ms.expected_turn_id), + params.client_user_message_id, params.responsesapi_client_metadata, ) .await @@ -989,8 +995,8 @@ impl TurnRequestProcessor { } else { vec![ThreadItem::UserMessage { id: turn_id.clone(), + client_id: None, content: vec![V2UserInput::Text { - client_id: None, text: display_text.to_string(), // Review prompt display text is synthesized; no UI element ranges to preserve. text_elements: Vec::new(), diff --git a/codex-rs/app-server/tests/suite/v2/account.rs b/codex-rs/app-server/tests/suite/v2/account.rs index fa6813c50b2..8eb55e8715a 100644 --- a/codex-rs/app-server/tests/suite/v2/account.rs +++ b/codex-rs/app-server/tests/suite/v2/account.rs @@ -506,8 +506,8 @@ async fn external_auth_refreshes_on_unauthorized() -> Result<()> { let turn_req = mcp .send_turn_start_request(codex_app_server_protocol::TurnStartParams { thread_id: thread.thread.id, + client_user_message_id: None, input: vec![codex_app_server_protocol::UserInput::Text { - client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], @@ -613,8 +613,8 @@ async fn external_auth_refresh_error_fails_turn() -> Result<()> { let turn_req = mcp .send_turn_start_request(codex_app_server_protocol::TurnStartParams { thread_id: thread.thread.id.clone(), + client_user_message_id: None, input: vec![codex_app_server_protocol::UserInput::Text { - client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], @@ -736,8 +736,8 @@ async fn external_auth_refresh_mismatched_workspace_fails_turn() -> Result<()> { let turn_req = mcp .send_turn_start_request(codex_app_server_protocol::TurnStartParams { thread_id: thread.thread.id.clone(), + client_user_message_id: None, input: vec![codex_app_server_protocol::UserInput::Text { - client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], @@ -852,8 +852,8 @@ async fn external_auth_refresh_invalid_access_token_fails_turn() -> Result<()> { let turn_req = mcp .send_turn_start_request(codex_app_server_protocol::TurnStartParams { thread_id: thread.thread.id.clone(), + client_user_message_id: None, input: vec![codex_app_server_protocol::UserInput::Text { - client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/attestation.rs b/codex-rs/app-server/tests/suite/v2/attestation.rs index 8a11be65a44..a662755be90 100644 --- a/codex-rs/app-server/tests/suite/v2/attestation.rs +++ b/codex-rs/app-server/tests/suite/v2/attestation.rs @@ -110,8 +110,8 @@ async fn attestation_generate_round_trip_adds_header_to_responses_websocket_hand let turn_request_id = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id, + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/client_metadata.rs b/codex-rs/app-server/tests/suite/v2/client_metadata.rs index 7c6b3409e0e..687efefbdf5 100644 --- a/codex-rs/app-server/tests/suite/v2/client_metadata.rs +++ b/codex-rs/app-server/tests/suite/v2/client_metadata.rs @@ -73,8 +73,8 @@ async fn turn_start_forwards_client_metadata_to_responses_request_v2() -> Result let turn_req = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id, + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], @@ -154,8 +154,8 @@ async fn turn_start_sends_fork_lineage_in_turn_metadata_for_thread_fork_v2() -> let turn_req = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "Continue".to_string(), text_elements: Vec::new(), }], @@ -335,8 +335,8 @@ async fn turn_steer_updates_client_metadata_on_follow_up_responses_request_v2() let turn_req = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "Run sleep".to_string(), text_elements: Vec::new(), }], @@ -366,8 +366,8 @@ async fn turn_steer_updates_client_metadata_on_follow_up_responses_request_v2() let steer_req = mcp .send_turn_steer_request(TurnSteerParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "Focus on the failure".to_string(), text_elements: Vec::new(), }], @@ -462,8 +462,8 @@ async fn turn_start_forwards_client_metadata_to_responses_websocket_request_body let turn_req = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id, + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/compaction.rs b/codex-rs/app-server/tests/suite/v2/compaction.rs index c4f9bda8987..ca46c88a285 100644 --- a/codex-rs/app-server/tests/suite/v2/compaction.rs +++ b/codex-rs/app-server/tests/suite/v2/compaction.rs @@ -394,8 +394,8 @@ async fn send_turn_and_wait(mcp: &mut McpProcess, thread_id: &str, text: &str) - let turn_id = mcp .send_turn_start_request(TurnStartParams { thread_id: thread_id.to_string(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: text.to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/connection_handling_websocket_unix.rs b/codex-rs/app-server/tests/suite/v2/connection_handling_websocket_unix.rs index 01d4c8e5123..0c8a59a9c85 100644 --- a/codex-rs/app-server/tests/suite/v2/connection_handling_websocket_unix.rs +++ b/codex-rs/app-server/tests/suite/v2/connection_handling_websocket_unix.rs @@ -226,8 +226,8 @@ async fn send_turn_start_request(stream: &mut WsClient, id: i64, thread_id: &str id, Some(serde_json::to_value(TurnStartParams { thread_id: thread_id.to_string(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/dynamic_tools.rs b/codex-rs/app-server/tests/suite/v2/dynamic_tools.rs index ac8edec221c..333086c722b 100644 --- a/codex-rs/app-server/tests/suite/v2/dynamic_tools.rs +++ b/codex-rs/app-server/tests/suite/v2/dynamic_tools.rs @@ -89,8 +89,8 @@ async fn thread_start_injects_dynamic_tools_into_model_requests() -> Result<()> let turn_req = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], @@ -169,8 +169,8 @@ async fn thread_start_keeps_hidden_dynamic_tools_out_of_model_requests() -> Resu let turn_req = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id, + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], @@ -349,8 +349,8 @@ async fn dynamic_tool_call_round_trip_sends_text_content_items_to_model() -> Res let turn_req = mcp .send_turn_start_request(TurnStartParams { thread_id: thread_id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "Run the tool".to_string(), text_elements: Vec::new(), }], @@ -524,8 +524,8 @@ async fn dynamic_tool_call_round_trip_sends_content_items_to_model() -> Result<( let turn_req = mcp .send_turn_start_request(TurnStartParams { thread_id: thread_id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "Run the tool".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/external_agent_config.rs b/codex-rs/app-server/tests/suite/v2/external_agent_config.rs index f42070e1c37..22790f94f79 100644 --- a/codex-rs/app-server/tests/suite/v2/external_agent_config.rs +++ b/codex-rs/app-server/tests/suite/v2/external_agent_config.rs @@ -391,8 +391,8 @@ async fn external_agent_config_import_creates_session_rollouts() -> Result<()> { let request_id = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![UserInput::Text { - client_id: None, text: "follow up".to_string(), text_elements: Vec::new(), }], @@ -965,8 +965,8 @@ async fn external_agent_config_import_compacts_huge_session_before_first_follow_ let request_id = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![UserInput::Text { - client_id: None, text: "follow up".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/hooks_list.rs b/codex-rs/app-server/tests/suite/v2/hooks_list.rs index 1c5e6736f82..7e0b39f0c6d 100644 --- a/codex-rs/app-server/tests/suite/v2/hooks_list.rs +++ b/codex-rs/app-server/tests/suite/v2/hooks_list.rs @@ -662,8 +662,8 @@ command = "python3 {hook_script_path}" let first_turn_id = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "first turn".to_string(), text_elements: Vec::new(), }], @@ -724,8 +724,8 @@ command = "python3 {hook_script_path}" let second_turn_id = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "second turn".to_string(), text_elements: Vec::new(), }], @@ -794,8 +794,8 @@ command = "python3 {hook_script_path}" let third_turn_id = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id, + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "third turn".to_string(), text_elements: Vec::new(), }], @@ -935,8 +935,8 @@ command = "python3 {hook_script_path}" let first_turn_id = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "first turn".to_string(), text_elements: Vec::new(), }], @@ -987,8 +987,8 @@ command = "python3 {hook_script_path}" let second_turn_id = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id, + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "second turn".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/initialize.rs b/codex-rs/app-server/tests/suite/v2/initialize.rs index 75d0edfb226..3a9b22cf16b 100644 --- a/codex-rs/app-server/tests/suite/v2/initialize.rs +++ b/codex-rs/app-server/tests/suite/v2/initialize.rs @@ -306,8 +306,8 @@ async fn turn_start_notify_payload_includes_initialize_client_name() -> Result<( let turn_req = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id, + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/mcp_server_elicitation.rs b/codex-rs/app-server/tests/suite/v2/mcp_server_elicitation.rs index 18e3d253df2..7357e3f1788 100644 --- a/codex-rs/app-server/tests/suite/v2/mcp_server_elicitation.rs +++ b/codex-rs/app-server/tests/suite/v2/mcp_server_elicitation.rs @@ -135,8 +135,8 @@ async fn mcp_server_elicitation_round_trip() -> Result<()> { let warmup_turn_start_id = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "Warm up connectors.".to_string(), text_elements: Vec::new(), }], @@ -168,8 +168,8 @@ async fn mcp_server_elicitation_round_trip() -> Result<()> { let turn_start_id = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "Use [$calendar](app://calendar) to run the calendar tool.".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/mcp_tool.rs b/codex-rs/app-server/tests/suite/v2/mcp_tool.rs index 809fcf24371..9ba09b078f3 100644 --- a/codex-rs/app-server/tests/suite/v2/mcp_tool.rs +++ b/codex-rs/app-server/tests/suite/v2/mcp_tool.rs @@ -461,8 +461,8 @@ url = "{mcp_server_url}/mcp" let turn_start_id = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id, + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "Call the large MCP tool".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/output_schema.rs b/codex-rs/app-server/tests/suite/v2/output_schema.rs index f2ab19c417d..86d12edcfc5 100644 --- a/codex-rs/app-server/tests/suite/v2/output_schema.rs +++ b/codex-rs/app-server/tests/suite/v2/output_schema.rs @@ -59,8 +59,8 @@ async fn turn_start_accepts_output_schema_v2() -> Result<()> { let turn_req = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], @@ -142,8 +142,8 @@ async fn turn_start_output_schema_is_per_turn_v2() -> Result<()> { let turn_req_1 = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], @@ -185,8 +185,8 @@ async fn turn_start_output_schema_is_per_turn_v2() -> Result<()> { let turn_req_2 = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "Hello again".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/plan_item.rs b/codex-rs/app-server/tests/suite/v2/plan_item.rs index 7681b21ef2b..63f7d9fba22 100644 --- a/codex-rs/app-server/tests/suite/v2/plan_item.rs +++ b/codex-rs/app-server/tests/suite/v2/plan_item.rs @@ -152,8 +152,8 @@ async fn start_plan_mode_turn(mcp: &mut McpProcess) -> Result Result<()> { let turn_start_id = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "pick a directory".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/request_user_input.rs b/codex-rs/app-server/tests/suite/v2/request_user_input.rs index ce73f23a020..a80b5527a7d 100644 --- a/codex-rs/app-server/tests/suite/v2/request_user_input.rs +++ b/codex-rs/app-server/tests/suite/v2/request_user_input.rs @@ -51,8 +51,8 @@ async fn request_user_input_round_trip() -> Result<()> { let turn_start_id = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "ask something".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/review.rs b/codex-rs/app-server/tests/suite/v2/review.rs index 27f5bedbf04..bb89a680775 100644 --- a/codex-rs/app-server/tests/suite/v2/review.rs +++ b/codex-rs/app-server/tests/suite/v2/review.rs @@ -91,8 +91,8 @@ async fn review_start_runs_review_turn_and_emits_code_review_item() -> Result<() turn.items, vec![ThreadItem::UserMessage { id: turn_id.clone(), + client_id: None, content: vec![V2UserInput::Text { - client_id: None, text: "commit 1234567: Tidy UI colors".to_string(), text_elements: Vec::new(), }], @@ -200,8 +200,8 @@ async fn review_start_exec_approval_item_id_matches_command_execution_item() -> turn.items, vec![ThreadItem::UserMessage { id: turn_id.clone(), + client_id: None, content: vec![V2UserInput::Text { - client_id: None, text: "commit 1234567: Check review approvals".to_string(), text_elements: Vec::new(), }], @@ -330,8 +330,8 @@ async fn review_start_with_detached_delivery_returns_new_thread_id() -> Result<( turn.items, vec![ThreadItem::UserMessage { id: turn.id.clone(), + client_id: None, content: vec![V2UserInput::Text { - client_id: None, text: "detached review".to_string(), text_elements: Vec::new(), }], @@ -468,8 +468,8 @@ async fn materialize_thread_rollout(mcp: &mut McpProcess, thread_id: &str) -> Re let turn_req = mcp .send_turn_start_request(TurnStartParams { thread_id: thread_id.to_string(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "materialize rollout".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/safety_check_downgrade.rs b/codex-rs/app-server/tests/suite/v2/safety_check_downgrade.rs index 7f5b2063870..a7f1bf3729b 100644 --- a/codex-rs/app-server/tests/suite/v2/safety_check_downgrade.rs +++ b/codex-rs/app-server/tests/suite/v2/safety_check_downgrade.rs @@ -67,8 +67,8 @@ async fn openai_model_header_mismatch_emits_model_rerouted_notification_v2() -> let turn_req = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![UserInput::Text { - client_id: None, text: "trigger safeguard".to_string(), text_elements: Vec::new(), }], @@ -134,8 +134,8 @@ async fn cyber_policy_response_emits_typed_error_notification_v2() -> Result<()> let turn_req = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![UserInput::Text { - client_id: None, text: "trigger cyber policy error".to_string(), text_elements: Vec::new(), }], @@ -211,8 +211,8 @@ async fn response_model_field_mismatch_emits_model_rerouted_notification_v2_when let turn_req = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![UserInput::Text { - client_id: None, text: "trigger response model check".to_string(), text_elements: Vec::new(), }], @@ -280,8 +280,8 @@ async fn model_verification_emits_typed_notification_and_warning_v2() -> Result< let turn_req = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![UserInput::Text { - client_id: None, text: "trigger model verification".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/thread_archive.rs b/codex-rs/app-server/tests/suite/v2/thread_archive.rs index d01cc7c7b10..6eadb1131b5 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_archive.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_archive.rs @@ -93,8 +93,8 @@ async fn thread_archive_requires_materialized_rollout() -> Result<()> { let turn_start_id = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![UserInput::Text { - client_id: None, text: "materialize".to_string(), text_elements: Vec::new(), }], @@ -518,8 +518,8 @@ async fn thread_archive_clears_stale_subscriptions_before_resume() -> Result<()> let turn_start_id = primary .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![UserInput::Text { - client_id: None, text: "materialize".to_string(), text_elements: Vec::new(), }], @@ -596,8 +596,8 @@ async fn thread_archive_clears_stale_subscriptions_before_resume() -> Result<()> let resumed_turn_id = secondary .send_turn_start_request(TurnStartParams { thread_id: thread.id, + client_user_message_id: None, input: vec![UserInput::Text { - client_id: None, text: "secondary turn".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/thread_fork.rs b/codex-rs/app-server/tests/suite/v2/thread_fork.rs index 8965c1c6d65..5c4c637f4b6 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_fork.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_fork.rs @@ -155,7 +155,6 @@ async fn thread_fork_creates_new_thread_and_emits_started() -> Result<()> { assert_eq!( content, &vec![UserInput::Text { - client_id: None, text: preview.to_string(), text_elements: Vec::new(), }] @@ -654,7 +653,6 @@ async fn thread_fork_ephemeral_remains_pathless_and_omits_listing() -> Result<() assert_eq!( content, &vec![UserInput::Text { - client_id: None, text: preview.to_string(), text_elements: Vec::new(), }] @@ -749,8 +747,8 @@ async fn thread_fork_ephemeral_remains_pathless_and_omits_listing() -> Result<() let turn_id = mcp .send_turn_start_request(TurnStartParams { thread_id: fork_thread_id, + client_user_message_id: None, input: vec![UserInput::Text { - client_id: None, text: "continue".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/thread_inject_items.rs b/codex-rs/app-server/tests/suite/v2/thread_inject_items.rs index 0965250fe5d..c543b504982 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_inject_items.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_inject_items.rs @@ -92,8 +92,8 @@ async fn thread_inject_items_adds_raw_response_items_to_thread_history() -> Resu let turn_req = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], @@ -171,8 +171,8 @@ async fn thread_inject_items_adds_raw_response_items_after_a_turn() -> Result<() let first_turn_req = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "First turn".to_string(), text_elements: Vec::new(), }], @@ -217,8 +217,8 @@ async fn thread_inject_items_adds_raw_response_items_after_a_turn() -> Result<() let second_turn_req = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "Second turn".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/thread_list.rs b/codex-rs/app-server/tests/suite/v2/thread_list.rs index 74ae440a370..88080923618 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_list.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_list.rs @@ -227,8 +227,8 @@ async fn thread_list_reports_system_error_idle_flag_after_failed_turn() -> Resul let seed_turn_id = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![UserInput::Text { - client_id: None, text: "seed history".to_string(), text_elements: Vec::new(), }], @@ -250,8 +250,8 @@ async fn thread_list_reports_system_error_idle_flag_after_failed_turn() -> Resul let failed_turn_id = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![UserInput::Text { - client_id: None, text: "fail turn".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/thread_read.rs b/codex-rs/app-server/tests/suite/v2/thread_read.rs index 2fae6a7b456..a53b354b6e1 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_read.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_read.rs @@ -185,7 +185,6 @@ async fn thread_read_can_include_turns() -> Result<()> { assert_eq!( content, &vec![UserInput::Text { - client_id: None, text: preview.to_string(), text_elements: text_elements.clone().into_iter().map(Into::into).collect(), }] @@ -1199,8 +1198,8 @@ async fn thread_read_reports_system_error_idle_flag_after_failed_turn() -> Resul let turn_start_id = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![UserInput::Text { - client_id: None, text: "fail this turn".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/thread_resume.rs b/codex-rs/app-server/tests/suite/v2/thread_resume.rs index b8539056fd0..36dac0d45ce 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_resume.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_resume.rs @@ -211,8 +211,8 @@ async fn thread_resume_with_empty_path_uses_running_thread_id() -> Result<()> { let turn_id = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![UserInput::Text { - client_id: None, text: "materialize rollout".to_string(), text_elements: Vec::new(), }], @@ -280,8 +280,8 @@ async fn turn_start_updates_runtime_workspace_roots_for_loaded_thread() -> Resul let turn_id = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![UserInput::Text { - client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], @@ -519,7 +519,6 @@ async fn thread_resume_returns_rollout_history() -> Result<()> { assert_eq!( content, &vec![UserInput::Text { - client_id: None, text: preview.to_string(), text_elements: text_elements.clone().into_iter().map(Into::into).collect(), }] @@ -812,8 +811,8 @@ async fn thread_resume_keeps_paused_goal_paused() -> Result<()> { let turn_id = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![UserInput::Text { - client_id: None, text: "materialize this thread".to_string(), text_elements: Vec::new(), }], @@ -917,8 +916,8 @@ async fn thread_goal_set_preserves_budget_limited_same_objective() -> Result<()> let turn_id = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![UserInput::Text { - client_id: None, text: "materialize this thread".to_string(), text_elements: Vec::new(), }], @@ -1016,8 +1015,8 @@ async fn thread_goal_set_persists_resumable_stopped_statuses() -> Result<()> { let turn_id = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![UserInput::Text { - client_id: None, text: "materialize this thread".to_string(), text_elements: Vec::new(), }], @@ -1212,8 +1211,8 @@ async fn thread_goal_clear_deletes_goal_and_notifies() -> Result<()> { let turn_id = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![UserInput::Text { - client_id: None, text: "materialize this thread".to_string(), text_elements: Vec::new(), }], @@ -2033,7 +2032,6 @@ async fn thread_resume_defers_updated_at_until_turn_start() -> Result<()> { .send_turn_start_request(TurnStartParams { thread_id, input: vec![UserInput::Text { - client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], @@ -2082,8 +2080,8 @@ async fn thread_resume_keeps_in_flight_turn_streaming() -> Result<()> { let seed_turn_id = primary .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![UserInput::Text { - client_id: None, text: "seed history".to_string(), text_elements: Vec::new(), }], @@ -2108,8 +2106,8 @@ async fn thread_resume_keeps_in_flight_turn_streaming() -> Result<()> { let turn_id = primary .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![UserInput::Text { - client_id: None, text: "respond with docs".to_string(), text_elements: Vec::new(), }], @@ -2191,8 +2189,8 @@ async fn thread_resume_rejects_history_when_thread_is_running() -> Result<()> { let seed_turn_id = primary .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![UserInput::Text { - client_id: None, text: "seed history".to_string(), text_elements: Vec::new(), }], @@ -2215,8 +2213,8 @@ async fn thread_resume_rejects_history_when_thread_is_running() -> Result<()> { let running_turn_request_id = primary .send_turn_start_request(TurnStartParams { thread_id: thread_id.clone(), + client_user_message_id: None, input: vec![UserInput::Text { - client_id: None, text: "keep running".to_string(), text_elements: Vec::new(), }], @@ -2309,8 +2307,8 @@ async fn thread_resume_rejects_mismatched_path_for_running_thread_id() -> Result let seed_turn_id = primary .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![UserInput::Text { - client_id: None, text: "seed history".to_string(), text_elements: Vec::new(), }], @@ -2333,8 +2331,8 @@ async fn thread_resume_rejects_mismatched_path_for_running_thread_id() -> Result let running_turn_request_id = primary .send_turn_start_request(TurnStartParams { thread_id: thread_id.clone(), + client_user_message_id: None, input: vec![UserInput::Text { - client_id: None, text: "keep running".to_string(), text_elements: Vec::new(), }], @@ -2447,8 +2445,8 @@ async fn thread_resume_rejoins_running_thread_even_with_override_mismatch() -> R let seed_turn_id = primary .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![UserInput::Text { - client_id: None, text: "seed history".to_string(), text_elements: Vec::new(), }], @@ -2470,8 +2468,8 @@ async fn thread_resume_rejoins_running_thread_even_with_override_mismatch() -> R let running_turn_id = primary .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![UserInput::Text { - client_id: None, text: "keep running".to_string(), text_elements: Vec::new(), }], @@ -2578,8 +2576,8 @@ async fn thread_resume_can_skip_turns_when_thread_is_running() -> Result<()> { let turn_id = primary .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![UserInput::Text { - client_id: None, text: "seed history".to_string(), text_elements: Vec::new(), }], @@ -2662,8 +2660,8 @@ async fn thread_resume_replays_pending_command_execution_request_approval() -> R let seed_turn_id = primary .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![UserInput::Text { - client_id: None, text: "seed history".to_string(), text_elements: Vec::new(), }], @@ -2685,8 +2683,8 @@ async fn thread_resume_replays_pending_command_execution_request_approval() -> R let running_turn_id = primary .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![UserInput::Text { - client_id: None, text: "run command".to_string(), text_elements: Vec::new(), }], @@ -2802,8 +2800,8 @@ async fn thread_resume_replays_pending_file_change_request_approval() -> Result< let seed_turn_id = primary .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![UserInput::Text { - client_id: None, text: "seed history".to_string(), text_elements: Vec::new(), }], @@ -2826,8 +2824,8 @@ async fn thread_resume_replays_pending_file_change_request_approval() -> Result< let running_turn_id = primary .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![UserInput::Text { - client_id: None, text: "apply patch".to_string(), text_elements: Vec::new(), }], @@ -2971,8 +2969,8 @@ async fn thread_resume_with_overrides_defers_updated_at_until_turn_start() -> Re let turn_id = mcp .send_turn_start_request(TurnStartParams { thread_id: resumed_thread.id, + client_user_message_id: None, input: vec![UserInput::Text { - client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], @@ -3284,8 +3282,8 @@ async fn start_materialized_thread_and_restart( let materialize_turn_id = first_mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![UserInput::Text { - client_id: None, text: seed_text.to_string(), text_elements: Vec::new(), }], @@ -3374,8 +3372,8 @@ async fn thread_resume_accepts_personality_override() -> Result<()> { let materialize_id = primary .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![UserInput::Text { - client_id: None, text: "seed history".to_string(), text_elements: Vec::new(), }], @@ -3415,8 +3413,8 @@ async fn thread_resume_accepts_personality_override() -> Result<()> { let turn_id = secondary .send_turn_start_request(TurnStartParams { thread_id: resume.thread.id, + client_user_message_id: None, input: vec![UserInput::Text { - client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/thread_rollback.rs b/codex-rs/app-server/tests/suite/v2/thread_rollback.rs index e5807b5aed6..80feacb4fef 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_rollback.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_rollback.rs @@ -57,8 +57,8 @@ async fn thread_rollback_drops_last_turns_and_persists_to_rollout() -> Result<() let turn1_id = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: first_text.to_string(), text_elements: Vec::new(), }], @@ -79,8 +79,8 @@ async fn thread_rollback_drops_last_turns_and_persists_to_rollout() -> Result<() let turn2_id = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "Second".to_string(), text_elements: Vec::new(), }], @@ -140,7 +140,6 @@ async fn thread_rollback_drops_last_turns_and_persists_to_rollout() -> Result<() assert_eq!( content, &vec![V2UserInput::Text { - client_id: None, text: first_text.to_string(), text_elements: Vec::new(), }] @@ -171,7 +170,6 @@ async fn thread_rollback_drops_last_turns_and_persists_to_rollout() -> Result<() assert_eq!( content, &vec![V2UserInput::Text { - client_id: None, text: first_text.to_string(), text_elements: Vec::new(), }] diff --git a/codex-rs/app-server/tests/suite/v2/thread_settings_update.rs b/codex-rs/app-server/tests/suite/v2/thread_settings_update.rs index 7b5069f6dad..5f7b8dbf21d 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_settings_update.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_settings_update.rs @@ -259,8 +259,8 @@ async fn turn_start_settings_override_emits_thread_settings_updated() -> Result< let turn_request_id = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "hello".to_string(), text_elements: Vec::new(), }], @@ -307,7 +307,6 @@ async fn start_text_turn(mcp: &mut McpProcess, thread_id: String) -> Result<()> .send_turn_start_request(TurnStartParams { thread_id, input: vec![V2UserInput::Text { - client_id: None, text: "hello".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/thread_shell_command.rs b/codex-rs/app-server/tests/suite/v2/thread_shell_command.rs index 19f52179d9d..368368badcf 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_shell_command.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_shell_command.rs @@ -275,8 +275,8 @@ async fn thread_shell_command_uses_existing_active_turn() -> Result<()> { let turn_id = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "run python".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/thread_status.rs b/codex-rs/app-server/tests/suite/v2/thread_status.rs index 3d75f021e86..51dda1bde71 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_status.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_status.rs @@ -48,8 +48,8 @@ async fn thread_status_changed_emits_runtime_updates() -> Result<()> { let turn_start_id = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "collect status updates".to_string(), text_elements: Vec::new(), }], @@ -172,8 +172,8 @@ async fn thread_status_changed_can_be_opted_out() -> Result<()> { let turn_start_id = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id, + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "run once".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/thread_unarchive.rs b/codex-rs/app-server/tests/suite/v2/thread_unarchive.rs index 70eec5db97f..dea8433d44d 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_unarchive.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_unarchive.rs @@ -81,8 +81,8 @@ async fn thread_unarchive_moves_rollout_back_into_sessions_directory() -> Result let turn_start_id = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![UserInput::Text { - client_id: None, text: "materialize".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/thread_unsubscribe.rs b/codex-rs/app-server/tests/suite/v2/thread_unsubscribe.rs index 237aa03c032..ebd3c9b95d6 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_unsubscribe.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_unsubscribe.rs @@ -151,8 +151,8 @@ async fn thread_unsubscribe_during_turn_keeps_turn_running() -> Result<()> { let turn_req = mcp .send_turn_start_request(TurnStartParams { thread_id: thread_id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "run deterministic tool".to_string(), text_elements: Vec::new(), }], @@ -261,8 +261,8 @@ async fn thread_unsubscribe_preserves_cached_status_before_idle_unload() -> Resu let turn_req = mcp .send_turn_start_request(TurnStartParams { thread_id: thread_id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "fail this turn".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/turn_interrupt.rs b/codex-rs/app-server/tests/suite/v2/turn_interrupt.rs index b4cb25fbfbb..78079ea148a 100644 --- a/codex-rs/app-server/tests/suite/v2/turn_interrupt.rs +++ b/codex-rs/app-server/tests/suite/v2/turn_interrupt.rs @@ -78,8 +78,8 @@ async fn turn_interrupt_aborts_running_turn() -> Result<()> { let turn_req = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "run sleep".to_string(), text_elements: Vec::new(), }], @@ -160,8 +160,8 @@ async fn turn_interrupt_rejects_completed_turn() -> Result<()> { let turn_req = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "say done".to_string(), text_elements: Vec::new(), }], @@ -255,8 +255,8 @@ async fn turn_interrupt_resolves_pending_command_approval_request() -> Result<() let turn_req = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "run python".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/turn_start.rs b/codex-rs/app-server/tests/suite/v2/turn_start.rs index 05b66177be5..2ae4561d861 100644 --- a/codex-rs/app-server/tests/suite/v2/turn_start.rs +++ b/codex-rs/app-server/tests/suite/v2/turn_start.rs @@ -141,8 +141,8 @@ async fn run_local_image_turn(detail: Option) -> Result> let turn_req = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::LocalImage { - client_id: None, path: image_path, detail, }], @@ -236,6 +236,7 @@ async fn turn_start_with_empty_input_runs_model_request() -> Result<()> { let turn_req = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: Vec::new(), ..Default::default() }) @@ -336,8 +337,8 @@ async fn turn_start_additional_context_flows_to_model_input() -> Result<()> { let turn_req = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id, + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "inspect tab".to_string(), text_elements: Vec::new(), }], @@ -422,8 +423,8 @@ async fn turn_start_sends_originator_header() -> Result<()> { let turn_req = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], @@ -495,8 +496,8 @@ async fn turn_start_emits_user_message_item_with_text_elements() -> Result<()> { let turn_req = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: Some("client-message-1".to_string()), input: vec![V2UserInput::Text { - client_id: None, text: "Hello".to_string(), text_elements: text_elements.clone(), }], @@ -525,11 +526,13 @@ async fn turn_start_emits_user_message_item_with_text_elements() -> Result<()> { .await??; match user_message_item { - ThreadItem::UserMessage { content, .. } => { + ThreadItem::UserMessage { + client_id, content, .. + } => { + assert_eq!(client_id, Some("client-message-1".to_string())); assert_eq!( content, vec![V2UserInput::Text { - client_id: None, text: "Hello".to_string(), text_elements, }] @@ -600,8 +603,8 @@ async fn turn_start_emits_thread_scoped_warning_notification_for_trimmed_skills( let turn_req = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], @@ -698,7 +701,6 @@ async fn turn_start_sends_service_tier_id_to_model_request() -> Result<()> { thread_id: thread.id, service_tier: Some(Some(service_tier_id.clone())), input: vec![V2UserInput::Text { - client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], @@ -768,8 +770,8 @@ async fn thread_start_omits_empty_instruction_overrides_from_model_request() -> let turn_req = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id, + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], @@ -848,8 +850,8 @@ async fn turn_start_tracks_turn_event_analytics() -> Result<()> { let turn_req = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Image { - client_id: None, url: "https://example.com/a.png".to_string(), detail: None, }], @@ -937,14 +939,13 @@ async fn turn_start_accepts_text_at_limit_with_mention_item() -> Result<()> { let turn_req = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id, + client_user_message_id: None, input: vec![ V2UserInput::Text { - client_id: None, text: "x".repeat(MAX_USER_INPUT_TEXT_CHARS), text_elements: Vec::new(), }, V2UserInput::Mention { - client_id: None, name: "Demo App".to_string(), path: "app://demo-app".to_string(), }, @@ -1002,14 +1003,13 @@ async fn turn_start_rejects_combined_oversized_text_input() -> Result<()> { let turn_req = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id, + client_user_message_id: None, input: vec![ V2UserInput::Text { - client_id: None, text: first, text_elements: Vec::new(), }, V2UserInput::Text { - client_id: None, text: second, text_elements: Vec::new(), }, @@ -1078,8 +1078,8 @@ async fn turn_start_rejects_invalid_permission_selection_before_starting_turn() let turn_req = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id, + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], @@ -1151,8 +1151,8 @@ async fn turn_start_rejects_unknown_environment_before_starting_turn() -> Result let turn_req = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id, + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], @@ -1225,8 +1225,8 @@ async fn turn_start_emits_notifications_and_accepts_model_override() -> Result<( let turn_req = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], @@ -1278,8 +1278,8 @@ async fn turn_start_emits_notifications_and_accepts_model_override() -> Result<( let turn_req2 = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "Second".to_string(), text_elements: Vec::new(), }], @@ -1377,8 +1377,8 @@ async fn turn_start_accepts_collaboration_mode_override_v2() -> Result<()> { let turn_req = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], @@ -1467,8 +1467,8 @@ async fn turn_start_uses_thread_feature_overrides_for_request_user_input_tool_de let turn_req = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], @@ -1539,8 +1539,8 @@ async fn turn_start_accepts_personality_override_v2() -> Result<()> { let turn_req = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], @@ -1621,8 +1621,8 @@ async fn turn_start_change_personality_mid_thread_v2() -> Result<()> { let turn_req = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], @@ -1646,8 +1646,8 @@ async fn turn_start_change_personality_mid_thread_v2() -> Result<()> { let turn_req2 = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "Hello again".to_string(), text_elements: Vec::new(), }], @@ -1749,8 +1749,8 @@ async fn turn_start_uses_migrated_pragmatic_personality_without_override_v2() -> let turn_req = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id, + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], @@ -1870,8 +1870,8 @@ async fn turn_start_exec_approval_toggle_v2() -> Result<()> { let first_turn_id = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "run python".to_string(), text_elements: Vec::new(), }], @@ -1935,8 +1935,8 @@ async fn turn_start_exec_approval_toggle_v2() -> Result<()> { let second_turn_id = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "run python again".to_string(), text_elements: Vec::new(), }], @@ -2013,8 +2013,8 @@ async fn turn_start_exec_approval_decline_v2() -> Result<()> { let turn_id = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "run python".to_string(), text_elements: Vec::new(), }], @@ -2169,8 +2169,8 @@ async fn turn_start_updates_sandbox_and_cwd_between_turns_v2() -> Result<()> { .send_turn_start_request(TurnStartParams { environments: None, thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "first turn".to_string(), text_elements: Vec::new(), }], @@ -2213,8 +2213,8 @@ async fn turn_start_updates_sandbox_and_cwd_between_turns_v2() -> Result<()> { .send_turn_start_request(TurnStartParams { environments: None, thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "second turn".to_string(), text_elements: Vec::new(), }], @@ -2356,8 +2356,8 @@ stream_max_retries = 0 let first_turn_id = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "select dev profile".to_string(), text_elements: Vec::new(), }], @@ -2380,8 +2380,8 @@ stream_max_retries = 0 let second_turn_id = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "write in new root".to_string(), text_elements: Vec::new(), }], @@ -2513,8 +2513,8 @@ async fn run_environment_selection_case( let turn_req = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: format!("run {}", case.name), text_elements: Vec::new(), }], @@ -2629,8 +2629,8 @@ async fn turn_start_file_change_approval_v2() -> Result<()> { let turn_req = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "apply patch".into(), text_elements: Vec::new(), }], @@ -2820,8 +2820,8 @@ async fn turn_start_does_not_stream_apply_patch_change_updates_without_feature_v let turn_req = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id, + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "apply patch".into(), text_elements: Vec::new(), }], @@ -2958,8 +2958,8 @@ async fn turn_start_streams_apply_patch_change_updates_v2() -> Result<()> { let turn_req = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "apply patch".into(), text_elements: Vec::new(), }], @@ -3089,8 +3089,8 @@ async fn turn_start_emits_spawn_agent_item_with_model_metadata_v2() -> Result<() let turn_req = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: PARENT_PROMPT.to_string(), text_elements: Vec::new(), }], @@ -3309,8 +3309,8 @@ config_file = "./custom-role.toml" let turn_req = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: PARENT_PROMPT.to_string(), text_elements: Vec::new(), }], @@ -3455,8 +3455,8 @@ async fn turn_start_file_change_approval_accept_for_session_persists_v2() -> Res let turn_1_req = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "apply patch 1".into(), text_elements: Vec::new(), }], @@ -3528,8 +3528,8 @@ async fn turn_start_file_change_approval_accept_for_session_persists_v2() -> Res let turn_2_req = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "apply patch 2".into(), text_elements: Vec::new(), }], @@ -3627,8 +3627,8 @@ async fn turn_start_file_change_approval_decline_v2() -> Result<()> { let turn_req = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "apply patch".into(), text_elements: Vec::new(), }], @@ -3773,8 +3773,8 @@ async fn command_execution_notifications_include_process_id() -> Result<()> { let turn_id = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "run a command".to_string(), text_elements: Vec::new(), }], @@ -3910,7 +3910,6 @@ async fn turn_start_with_elevated_override_does_not_persist_project_trust() -> R cwd: Some(workspace.path().to_path_buf()), sandbox_policy: Some(codex_app_server_protocol::SandboxPolicy::DangerFullAccess), input: vec![V2UserInput::Text { - client_id: None, text: "Hello".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/turn_start_zsh_fork.rs b/codex-rs/app-server/tests/suite/v2/turn_start_zsh_fork.rs index f67e683ca32..ec77af88dd3 100644 --- a/codex-rs/app-server/tests/suite/v2/turn_start_zsh_fork.rs +++ b/codex-rs/app-server/tests/suite/v2/turn_start_zsh_fork.rs @@ -117,8 +117,8 @@ async fn turn_start_shell_zsh_fork_executes_command_v2() -> Result<()> { let turn_id = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "run echo hi".to_string(), text_elements: Vec::new(), }], @@ -236,8 +236,8 @@ async fn turn_start_shell_zsh_fork_exec_approval_decline_v2() -> Result<()> { let turn_id = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "run python".to_string(), text_elements: Vec::new(), }], @@ -369,8 +369,8 @@ async fn turn_start_shell_zsh_fork_exec_approval_cancel_v2() -> Result<()> { let turn_id = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "run python".to_string(), text_elements: Vec::new(), }], @@ -528,8 +528,8 @@ async fn turn_start_shell_zsh_fork_subcommand_decline_marks_parent_declined_v2() let turn_id = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "remove both files".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/app-server/tests/suite/v2/turn_steer.rs b/codex-rs/app-server/tests/suite/v2/turn_steer.rs index 770e0a24953..3d4b46b91dc 100644 --- a/codex-rs/app-server/tests/suite/v2/turn_steer.rs +++ b/codex-rs/app-server/tests/suite/v2/turn_steer.rs @@ -12,10 +12,12 @@ use codex_app_server::INPUT_TOO_LARGE_ERROR_CODE; use codex_app_server::INVALID_PARAMS_ERROR_CODE; use codex_app_server_protocol::AdditionalContextEntry; use codex_app_server_protocol::AdditionalContextKind; +use codex_app_server_protocol::ItemStartedNotification; use codex_app_server_protocol::JSONRPCError; use codex_app_server_protocol::JSONRPCNotification; use codex_app_server_protocol::JSONRPCResponse; use codex_app_server_protocol::RequestId; +use codex_app_server_protocol::ThreadItem; use codex_app_server_protocol::ThreadStartParams; use codex_app_server_protocol::ThreadStartResponse; use codex_app_server_protocol::TurnStartParams; @@ -67,8 +69,8 @@ async fn turn_steer_requires_active_turn() -> Result<()> { let steer_req = mcp .send_turn_steer_request(TurnSteerParams { thread_id: thread.id.clone(), + client_user_message_id: Some("client-steer-message-1".to_string()), input: vec![V2UserInput::Text { - client_id: None, text: "steer".to_string(), text_elements: Vec::new(), }], @@ -153,8 +155,8 @@ async fn turn_steer_rejects_oversized_text_input() -> Result<()> { let turn_req = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "run sleep".to_string(), text_elements: Vec::new(), }], @@ -179,8 +181,8 @@ async fn turn_steer_rejects_oversized_text_input() -> Result<()> { let steer_req = mcp .send_turn_steer_request(TurnSteerParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: oversized_input.clone(), text_elements: Vec::new(), }], @@ -220,10 +222,10 @@ async fn turn_steer_returns_active_turn_id() -> Result<()> { let shell_command = vec![ "powershell".to_string(), "-Command".to_string(), - "Start-Sleep -Seconds 10".to_string(), + "Start-Sleep -Seconds 2".to_string(), ]; #[cfg(not(target_os = "windows"))] - let shell_command = vec!["sleep".to_string(), "10".to_string()]; + let shell_command = vec!["sleep".to_string(), "2".to_string()]; let tmp = TempDir::new()?; let codex_home = tmp.path().join("codex_home"); @@ -231,14 +233,16 @@ async fn turn_steer_returns_active_turn_id() -> Result<()> { let working_directory = tmp.path().join("workdir"); std::fs::create_dir(&working_directory)?; - let server = - create_mock_responses_server_sequence_unchecked(vec![create_shell_command_sse_response( + let server = create_mock_responses_server_sequence_unchecked(vec![ + create_shell_command_sse_response( shell_command.clone(), Some(&working_directory), Some(10_000), "call_sleep", - )?]) - .await; + )?, + app_test_support::create_final_assistant_message_sse_response("Done")?, + ]) + .await; write_mock_responses_config_toml_with_chatgpt_base_url( &codex_home, &server.uri(), @@ -265,8 +269,8 @@ async fn turn_steer_returns_active_turn_id() -> Result<()> { let turn_req = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "run sleep".to_string(), text_elements: Vec::new(), }], @@ -290,8 +294,8 @@ async fn turn_steer_returns_active_turn_id() -> Result<()> { let steer_req = mcp .send_turn_steer_request(TurnSteerParams { thread_id: thread.id.clone(), + client_user_message_id: Some("client-steer-message-1".to_string()), input: vec![V2UserInput::Text { - client_id: None, text: "steer".to_string(), text_elements: Vec::new(), }], @@ -308,6 +312,34 @@ async fn turn_steer_returns_active_turn_id() -> Result<()> { let steer: TurnSteerResponse = to_response::(steer_resp)?; assert_eq!(steer.turn_id, turn.id); + timeout(DEFAULT_READ_TIMEOUT, async { + loop { + let notification = mcp + .read_stream_until_notification_message("item/started") + .await?; + let params = notification.params.expect("item/started params"); + let item_started: ItemStartedNotification = + serde_json::from_value(params).expect("deserialize item/started notification"); + let ThreadItem::UserMessage { + client_id, content, .. + } = item_started.item + else { + continue; + }; + if client_id == Some("client-steer-message-1".to_string()) { + assert_eq!( + content, + vec![V2UserInput::Text { + text: "steer".to_string(), + text_elements: Vec::new(), + }] + ); + return Ok::<(), anyhow::Error>(()); + } + } + }) + .await??; + let event = wait_for_analytics_event(&server, DEFAULT_READ_TIMEOUT, "codex_turn_steer_event").await?; assert_eq!(event["event_params"]["thread_id"], thread.id); @@ -321,8 +353,11 @@ async fn turn_steer_returns_active_turn_id() -> Result<()> { serde_json::Value::Null ); - mcp.interrupt_turn_and_wait_for_aborted(thread.id, steer.turn_id, DEFAULT_READ_TIMEOUT) - .await?; + timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_notification_message("turn/completed"), + ) + .await??; Ok(()) } @@ -371,8 +406,8 @@ async fn turn_steer_rejects_context_only_input_without_merging_context() -> Resu let turn_req = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "run sleep".to_string(), text_elements: Vec::new(), }], @@ -402,6 +437,7 @@ async fn turn_steer_rejects_context_only_input_without_merging_context() -> Resu let steer_req = mcp .send_turn_steer_request(TurnSteerParams { thread_id: thread.id.clone(), + client_user_message_id: None, input: Vec::new(), responsesapi_client_metadata: None, additional_context, diff --git a/codex-rs/app-server/tests/suite/v2/web_search.rs b/codex-rs/app-server/tests/suite/v2/web_search.rs index 8a1ef612883..5cc02c7f6f1 100644 --- a/codex-rs/app-server/tests/suite/v2/web_search.rs +++ b/codex-rs/app-server/tests/suite/v2/web_search.rs @@ -88,8 +88,8 @@ async fn standalone_web_search_round_trips_encrypted_output() -> Result<()> { let turn_req = mcp .send_turn_start_request(TurnStartParams { thread_id: thread.id, + client_user_message_id: None, input: vec![V2UserInput::Text { - client_id: None, text: "Search the web".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/cli/src/main.rs b/codex-rs/cli/src/main.rs index aaa0f508f8f..e0812e3fdc9 100644 --- a/codex-rs/cli/src/main.rs +++ b/codex-rs/cli/src/main.rs @@ -1742,15 +1742,10 @@ async fn run_debug_prompt_input_command( .images .into_iter() .chain(cmd.images) - .map(|path| UserInput::LocalImage { - client_id: None, - path, - detail: None, - }) + .map(|path| UserInput::LocalImage { path, detail: None }) .collect::>(); if let Some(prompt) = cmd.prompt.or(interactive.prompt) { input.push(UserInput::Text { - client_id: None, text: prompt.replace("\r\n", "\n").replace('\r', "\n"), text_elements: Vec::new(), }); diff --git a/codex-rs/core-skills/src/injection_tests.rs b/codex-rs/core-skills/src/injection_tests.rs index 5ff8f38f2b3..78aa1958952 100644 --- a/codex-rs/core-skills/src/injection_tests.rs +++ b/codex-rs/core-skills/src/injection_tests.rs @@ -136,7 +136,6 @@ fn collect_explicit_skill_mentions_text_respects_skill_order() { let beta = make_skill("beta-skill", "/tmp/beta"); let skills = vec![beta.clone(), alpha.clone()]; let inputs = vec![UserInput::Text { - client_id: None, text: "first $alpha-skill then $beta-skill".to_string(), text_elements: Vec::new(), }]; @@ -155,12 +154,10 @@ fn collect_explicit_skill_mentions_prioritizes_structured_inputs() { let skills = vec![alpha.clone(), beta.clone()]; let inputs = vec![ UserInput::Text { - client_id: None, text: "please run $alpha-skill".to_string(), text_elements: Vec::new(), }, UserInput::Skill { - client_id: None, name: "beta-skill".to_string(), path: test_path_buf("/tmp/beta"), }, @@ -178,12 +175,10 @@ fn collect_explicit_skill_mentions_skips_invalid_structured_and_blocks_plain_fal let skills = vec![alpha]; let inputs = vec![ UserInput::Text { - client_id: None, text: "please run $alpha-skill".to_string(), text_elements: Vec::new(), }, UserInput::Skill { - client_id: None, name: "alpha-skill".to_string(), path: test_path_buf("/tmp/missing"), }, @@ -201,12 +196,10 @@ fn collect_explicit_skill_mentions_skips_disabled_structured_and_blocks_plain_fa let skills = vec![alpha]; let inputs = vec![ UserInput::Text { - client_id: None, text: "please run $alpha-skill".to_string(), text_elements: Vec::new(), }, UserInput::Skill { - client_id: None, name: "alpha-skill".to_string(), path: test_path_buf("/tmp/alpha"), }, @@ -225,7 +218,6 @@ fn collect_explicit_skill_mentions_dedupes_by_path() { let skills = vec![alpha.clone()]; let mention = linked_skill_mention("alpha-skill", "/tmp/alpha"); let inputs = vec![UserInput::Text { - client_id: None, text: format!("use {mention} and {mention}"), text_elements: Vec::new(), }]; @@ -242,7 +234,6 @@ fn collect_explicit_skill_mentions_skips_ambiguous_name() { let beta = make_skill("demo-skill", "/tmp/beta"); let skills = vec![alpha, beta]; let inputs = vec![UserInput::Text { - client_id: None, text: "use $demo-skill and again $demo-skill".to_string(), text_elements: Vec::new(), }]; @@ -259,7 +250,6 @@ fn collect_explicit_skill_mentions_prefers_linked_path_over_name() { let beta = make_skill("demo-skill", "/tmp/beta"); let skills = vec![alpha, beta.clone()]; let inputs = vec![UserInput::Text { - client_id: None, text: format!( "use $demo-skill and {}", linked_skill_mention("demo-skill", "/tmp/beta") @@ -278,7 +268,6 @@ fn collect_explicit_skill_mentions_skips_plain_name_when_connector_matches() { let alpha = make_skill("alpha-skill", "/tmp/alpha"); let skills = vec![alpha]; let inputs = vec![UserInput::Text { - client_id: None, text: "use $alpha-skill".to_string(), text_elements: Vec::new(), }]; @@ -294,7 +283,6 @@ fn collect_explicit_skill_mentions_allows_explicit_path_with_connector_conflict( let alpha = make_skill("alpha-skill", "/tmp/alpha"); let skills = vec![alpha.clone()]; let inputs = vec![UserInput::Text { - client_id: None, text: format!("use {}", linked_skill_mention("alpha-skill", "/tmp/alpha")), text_elements: Vec::new(), }]; @@ -311,7 +299,6 @@ fn collect_explicit_skill_mentions_skips_when_linked_path_disabled() { let beta = make_skill("demo-skill", "/tmp/beta"); let skills = vec![alpha, beta]; let inputs = vec![UserInput::Text { - client_id: None, text: format!("use {}", linked_skill_mention("demo-skill", "/tmp/alpha")), text_elements: Vec::new(), }]; @@ -329,7 +316,6 @@ fn collect_explicit_skill_mentions_prefers_resource_path() { let beta = make_skill("demo-skill", "/tmp/beta"); let skills = vec![alpha, beta.clone()]; let inputs = vec![UserInput::Text { - client_id: None, text: format!("use {}", linked_skill_mention("demo-skill", "/tmp/beta")), text_elements: Vec::new(), }]; @@ -346,7 +332,6 @@ fn collect_explicit_skill_mentions_skips_missing_path_with_no_fallback() { let beta = make_skill("demo-skill", "/tmp/beta"); let skills = vec![alpha, beta]; let inputs = vec![UserInput::Text { - client_id: None, text: format!("use {}", linked_skill_mention("demo-skill", "/tmp/missing")), text_elements: Vec::new(), }]; @@ -362,7 +347,6 @@ fn collect_explicit_skill_mentions_skips_missing_path_without_fallback() { let alpha = make_skill("demo-skill", "/tmp/alpha"); let skills = vec![alpha]; let inputs = vec![UserInput::Text { - client_id: None, text: format!("use {}", linked_skill_mention("demo-skill", "/tmp/missing")), text_elements: Vec::new(), }]; diff --git a/codex-rs/core/src/agent/control_tests.rs b/codex-rs/core/src/agent/control_tests.rs index 582bdc14a46..43a6e47bfdb 100644 --- a/codex-rs/core/src/agent/control_tests.rs +++ b/codex-rs/core/src/agent/control_tests.rs @@ -57,7 +57,6 @@ async fn test_config() -> (TempDir, Config) { fn text_input(text: &str) -> Op { vec![UserInput::Text { - client_id: None, text: text.to_string(), text_elements: Vec::new(), }] @@ -248,7 +247,6 @@ async fn send_input_errors_when_manager_dropped() { .send_input( ThreadId::new(), vec![UserInput::Text { - client_id: None, text: "hello".to_string(), text_elements: Vec::new(), }] @@ -361,7 +359,6 @@ async fn send_input_errors_when_thread_missing() { .send_input( thread_id, vec![UserInput::Text { - client_id: None, text: "hello".to_string(), text_elements: Vec::new(), }] @@ -429,7 +426,6 @@ async fn send_input_submits_user_message() { .send_input( thread_id, vec![UserInput::Text { - client_id: None, text: "hello from tests".to_string(), text_elements: Vec::new(), }] @@ -443,7 +439,6 @@ async fn send_input_submits_user_message() { Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello from tests".to_string(), text_elements: Vec::new(), }], @@ -545,7 +540,6 @@ async fn spawn_agent_creates_thread_and_sends_prompt() { Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "spawned".to_string(), text_elements: Vec::new(), }], @@ -719,7 +713,6 @@ async fn spawn_agent_can_fork_parent_thread_history_with_sanitized_items() { Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "child task".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/src/codex_delegate.rs b/codex-rs/core/src/codex_delegate.rs index 2afbeb45c96..3c55f637614 100644 --- a/codex-rs/core/src/codex_delegate.rs +++ b/codex-rs/core/src/codex_delegate.rs @@ -218,6 +218,7 @@ pub(crate) async fn run_codex_thread_one_shot( .send(Submission { id: "shutdown".to_string(), op: Op::Shutdown {}, + client_user_message_id: None, trace: None, }) .await; diff --git a/codex-rs/core/src/codex_delegate_tests.rs b/codex-rs/core/src/codex_delegate_tests.rs index 66cde8d1ea5..04f6ae39659 100644 --- a/codex-rs/core/src/codex_delegate_tests.rs +++ b/codex-rs/core/src/codex_delegate_tests.rs @@ -129,6 +129,7 @@ async fn forward_ops_preserves_submission_trace_context() { let submission = Submission { id: "sub-1".to_string(), op: Op::Interrupt, + client_user_message_id: None, trace: Some(codex_protocol::protocol::W3cTraceContext { traceparent: Some( "00-1234567890abcdef1234567890abcdef-1234567890abcdef-01".to_string(), diff --git a/codex-rs/core/src/codex_thread.rs b/codex-rs/core/src/codex_thread.rs index ce8000de6d8..21f9ec4549c 100644 --- a/codex-rs/core/src/codex_thread.rs +++ b/codex-rs/core/src/codex_thread.rs @@ -230,6 +230,17 @@ impl CodexThread { self.codex.submit_with_trace(op, trace).await } + pub async fn submit_user_input_with_client_user_message_id( + &self, + op: Op, + trace: Option, + client_user_message_id: Option, + ) -> CodexResult { + self.codex + .submit_user_input_with_client_user_message_id(op, trace, client_user_message_id) + .await + } + /// Persist whether this thread is eligible for future memory generation. pub async fn set_thread_memory_mode(&self, mode: ThreadMemoryMode) -> anyhow::Result<()> { self.codex.set_thread_memory_mode(mode).await @@ -240,6 +251,7 @@ impl CodexThread { input: Vec, additional_context: BTreeMap, expected_turn_id: Option<&str>, + client_user_message_id: Option, responsesapi_client_metadata: Option>, ) -> Result { self.codex @@ -247,6 +259,7 @@ impl CodexThread { input, additional_context, expected_turn_id, + client_user_message_id, responsesapi_client_metadata, ) .await diff --git a/codex-rs/core/src/compact.rs b/codex-rs/core/src/compact.rs index 51c8b44ab92..fd388b273cf 100644 --- a/codex-rs/core/src/compact.rs +++ b/codex-rs/core/src/compact.rs @@ -76,7 +76,6 @@ pub(crate) async fn run_inline_auto_compact_task( ) -> CodexResult<()> { let prompt = turn_context.compact_prompt().to_string(); let input = vec![UserInput::Text { - client_id: None, text: prompt, // Compaction prompt is synthesized; no UI element ranges to preserve. text_elements: Vec::new(), diff --git a/codex-rs/core/src/event_mapping.rs b/codex-rs/core/src/event_mapping.rs index 143941be68b..d3cae7feed4 100644 --- a/codex-rs/core/src/event_mapping.rs +++ b/codex-rs/core/src/event_mapping.rs @@ -85,7 +85,6 @@ fn parse_user_message(message: &[ContentItem]) -> Option { continue; } content.push(UserInput::Text { - client_id: None, text: text.clone(), // Model input content does not carry UI element ranges. text_elements: Vec::new(), @@ -93,7 +92,6 @@ fn parse_user_message(message: &[ContentItem]) -> Option { } ContentItem::InputImage { image_url, detail } => { content.push(UserInput::Image { - client_id: None, image_url: image_url.clone(), detail: *detail, }); diff --git a/codex-rs/core/src/event_mapping_tests.rs b/codex-rs/core/src/event_mapping_tests.rs index 0985abd6b56..57d6362f55d 100644 --- a/codex-rs/core/src/event_mapping_tests.rs +++ b/codex-rs/core/src/event_mapping_tests.rs @@ -45,17 +45,14 @@ fn parses_user_message_with_text_and_two_images() { TurnItem::UserMessage(user) => { let expected_content = vec![ UserInput::Text { - client_id: None, text: "Hello world".to_string(), text_elements: Vec::new(), }, UserInput::Image { - client_id: None, image_url: img1, detail: Some(DEFAULT_IMAGE_DETAIL), }, UserInput::Image { - client_id: None, image_url: img2, detail: Some(DEFAULT_IMAGE_DETAIL), }, @@ -97,12 +94,10 @@ fn skips_local_image_label_text() { TurnItem::UserMessage(user) => { let expected_content = vec![ UserInput::Image { - client_id: None, image_url, detail: Some(DEFAULT_IMAGE_DETAIL), }, UserInput::Text { - client_id: None, text: user_text, text_elements: Vec::new(), }, @@ -180,12 +175,10 @@ fn skips_unnamed_image_label_text() { TurnItem::UserMessage(user) => { let expected_content = vec![ UserInput::Image { - client_id: None, image_url, detail: Some(DEFAULT_IMAGE_DETAIL), }, UserInput::Text { - client_id: None, text: user_text, text_elements: Vec::new(), }, diff --git a/codex-rs/core/src/guardian/prompt.rs b/codex-rs/core/src/guardian/prompt.rs index 4310cd772da..b1b132a9844 100644 --- a/codex-rs/core/src/guardian/prompt.rs +++ b/codex-rs/core/src/guardian/prompt.rs @@ -153,7 +153,6 @@ pub(crate) async fn build_guardian_prompt_items( let mut items = Vec::new(); let mut push_text = |text: String| { items.push(UserInput::Text { - client_id: None, text, text_elements: Vec::new(), }); diff --git a/codex-rs/core/src/hook_runtime.rs b/codex-rs/core/src/hook_runtime.rs index 56d51412cfe..80a19974772 100644 --- a/codex-rs/core/src/hook_runtime.rs +++ b/codex-rs/core/src/hook_runtime.rs @@ -500,7 +500,7 @@ pub(crate) async fn inspect_pending_input( pending_input_item: &TurnInput, ) -> HookRuntimeOutcome { match pending_input_item { - TurnInput::UserInput(content) => { + TurnInput::UserInput { content, .. } => { let request = UserPromptSubmitRequest { session_id: sess.session_id().into(), turn_id: turn_context.sub_id.clone(), @@ -536,9 +536,13 @@ pub(crate) async fn record_pending_input( additional_contexts: Vec, ) { match pending_input { - TurnInput::UserInput(content) => { - sess.record_user_prompt_and_emit_turn_item(turn_context.as_ref(), content.as_slice()) - .await; + TurnInput::UserInput { content, client_id } => { + sess.record_user_prompt_and_emit_turn_item( + turn_context.as_ref(), + content.as_slice(), + client_id, + ) + .await; } TurnInput::ResponseItem(item) => { sess.record_conversation_items(turn_context, std::slice::from_ref(&item)) diff --git a/codex-rs/core/src/plugins/mentions_tests.rs b/codex-rs/core/src/plugins/mentions_tests.rs index 8596ad24e42..37c9adb886b 100644 --- a/codex-rs/core/src/plugins/mentions_tests.rs +++ b/codex-rs/core/src/plugins/mentions_tests.rs @@ -9,7 +9,6 @@ use crate::plugins::PluginCapabilitySummary; fn text_input(text: &str) -> UserInput { UserInput::Text { - client_id: None, text: text.to_string(), text_elements: Vec::new(), } @@ -40,7 +39,6 @@ fn collect_explicit_app_ids_dedupes_structured_and_linked_mentions() { let input = vec![ text_input("use [$calendar](app://calendar)"), UserInput::Mention { - client_id: None, name: "calendar".to_string(), path: "app://calendar".to_string(), }, @@ -58,17 +56,14 @@ fn collect_explicit_app_ids_ignores_non_app_paths() { "use [$docs](mcp://docs) and [$skill](skill://team/skill) and [$file](/tmp/file.txt)", ), UserInput::Mention { - client_id: None, name: "docs".to_string(), path: "mcp://docs".to_string(), }, UserInput::Mention { - client_id: None, name: "skill".to_string(), path: "skill://team/skill".to_string(), }, UserInput::Mention { - client_id: None, name: "file".to_string(), path: "/tmp/file.txt".to_string(), }, @@ -88,7 +83,6 @@ fn collect_explicit_plugin_mentions_from_structured_paths() { let mentioned = collect_explicit_plugin_mentions( &[UserInput::Mention { - client_id: None, name: "sample".to_string(), path: "plugin://sample@test".to_string(), }], @@ -124,7 +118,6 @@ fn collect_explicit_plugin_mentions_dedupes_structured_and_linked_mentions() { &[ text_input("use [@sample](plugin://sample@test)"), UserInput::Mention { - client_id: None, name: "sample".to_string(), path: "plugin://sample@test".to_string(), }, diff --git a/codex-rs/core/src/session/handlers.rs b/codex-rs/core/src/session/handlers.rs index 0a72d272340..135ea611940 100644 --- a/codex-rs/core/src/session/handlers.rs +++ b/codex-rs/core/src/session/handlers.rs @@ -85,12 +85,18 @@ pub async fn realtime_conversation_list_voices(sess: &Session, sub_id: String) { .await; } -pub async fn user_input_or_turn(sess: &Arc, sub_id: String, op: Op) { +pub async fn user_input_or_turn( + sess: &Arc, + sub_id: String, + op: Op, + client_user_message_id: Option, +) { user_input_or_turn_inner( sess, sub_id, op, /*mirror_user_text_to_realtime*/ Some(()), + client_user_message_id, ) .await; } @@ -190,6 +196,7 @@ pub(super) async fn user_input_or_turn_inner( sub_id: String, op: Op, mirror_user_text_to_realtime: Option<()>, + client_user_message_id: Option, ) { let Op::UserInput { items, @@ -229,6 +236,7 @@ pub(super) async fn user_input_or_turn_inner( items.clone(), additional_context.clone(), /*expected_turn_id*/ None, + client_user_message_id.clone(), responsesapi_client_metadata.clone(), ) .await @@ -260,7 +268,10 @@ pub(super) async fn user_input_or_turn_inner( .map(TurnInput::ResponseItem) .collect::>(); if !items.is_empty() { - task_input.push(TurnInput::UserInput(items)); + task_input.push(TurnInput::UserInput { + content: items, + client_id: client_user_message_id, + }); } sess.spawn_task( Arc::clone(¤t_context), @@ -773,7 +784,8 @@ pub(super) async fn submission_loop( false } Op::UserInput { .. } => { - user_input_or_turn(&sess, sub.id.clone(), sub.op).await; + user_input_or_turn(&sess, sub.id.clone(), sub.op, sub.client_user_message_id) + .await; false } Op::ThreadSettings { thread_settings } => { diff --git a/codex-rs/core/src/session/input_queue.rs b/codex-rs/core/src/session/input_queue.rs index e317ba57a3b..b62383e0915 100644 --- a/codex-rs/core/src/session/input_queue.rs +++ b/codex-rs/core/src/session/input_queue.rs @@ -11,7 +11,10 @@ use tokio::sync::watch; #[derive(Clone, Debug, PartialEq)] pub(crate) enum TurnInput { - UserInput(Vec), + UserInput { + content: Vec, + client_id: Option, + }, ResponseItem(ResponseItem), } diff --git a/codex-rs/core/src/session/mod.rs b/codex-rs/core/src/session/mod.rs index d79f90adba0..8c535cd7287 100644 --- a/codex-rs/core/src/session/mod.rs +++ b/codex-rs/core/src/session/mod.rs @@ -667,6 +667,25 @@ impl Codex { let sub = Submission { id: id.clone(), op, + client_user_message_id: None, + trace, + }; + self.submit_with_id(sub).await?; + Ok(id) + } + + pub async fn submit_user_input_with_client_user_message_id( + &self, + op: Op, + trace: Option, + client_user_message_id: Option, + ) -> CodexResult { + debug_assert!(matches!(op, Op::UserInput { .. })); + let id = Uuid::now_v7().to_string(); + let sub = Submission { + id: id.clone(), + op, + client_user_message_id, trace, }; self.submit_with_id(sub).await?; @@ -722,6 +741,7 @@ impl Codex { input: Vec, additional_context: BTreeMap, expected_turn_id: Option<&str>, + client_user_message_id: Option, responsesapi_client_metadata: Option>, ) -> Result { self.session @@ -729,6 +749,7 @@ impl Codex { input, additional_context, expected_turn_id, + client_user_message_id, responsesapi_client_metadata, ) .await @@ -1065,7 +1086,6 @@ impl Session { Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text, text_elements: Vec::new(), }], @@ -1075,6 +1095,7 @@ impl Session { thread_settings: Default::default(), }, /*mirror_user_text_to_realtime*/ None, + /*client_user_message_id*/ None, ) .await; } @@ -3057,6 +3078,7 @@ impl Session { &self, turn_context: &TurnContext, input: &[UserInput], + client_id: Option, ) { // Persist the user message to history, but emit the turn item from `UserInput` so // UI-only `text_elements` are preserved. `ResponseItem::Message` does not carry @@ -3064,7 +3086,9 @@ impl Session { let response_item = ResponseItem::from(ResponseInputItem::from(input.to_vec())); self.record_conversation_items(turn_context, std::slice::from_ref(&response_item)) .await; - let turn_item = TurnItem::UserMessage(UserMessageItem::new(input)); + let mut user_message_item = UserMessageItem::new(input); + user_message_item.client_id = client_id; + let turn_item = TurnItem::UserMessage(user_message_item); self.emit_turn_item_started(turn_context, &turn_item).await; self.emit_turn_item_completed(turn_context, turn_item).await; self.ensure_rollout_materialized().await; @@ -3100,6 +3124,7 @@ impl Session { input: Vec, additional_context: BTreeMap, expected_turn_id: Option<&str>, + client_user_message_id: Option, responsesapi_client_metadata: Option>, ) -> Result { let mut active = self.active_turn.lock().await; @@ -3156,7 +3181,10 @@ impl Session { .map(ResponseItem::from) .map(TurnInput::ResponseItem) .collect::>(); - pending_input.push(TurnInput::UserInput(input)); + pending_input.push(TurnInput::UserInput { + content: input, + client_id: client_user_message_id, + }); self.input_queue .extend_pending_input_and_accept_mailbox_delivery_for_turn_state( active_turn.turn_state.as_ref(), diff --git a/codex-rs/core/src/session/review.rs b/codex-rs/core/src/session/review.rs index 58057fd173e..75b7539090f 100644 --- a/codex-rs/core/src/session/review.rs +++ b/codex-rs/core/src/session/review.rs @@ -137,12 +137,14 @@ pub(super) async fn spawn_review_thread( }; // Seed the child task with the review prompt as the initial user message. - let input = vec![TurnInput::UserInput(vec![UserInput::Text { + let input = vec![TurnInput::UserInput { + content: vec![UserInput::Text { + text: review_prompt, + // Review prompt is synthesized; no UI element ranges to preserve. + text_elements: Vec::new(), + }], client_id: None, - text: review_prompt, - // Review prompt is synthesized; no UI element ranges to preserve. - text_elements: Vec::new(), - }])]; + }]; let tc = Arc::new(review_turn_context); tc.turn_metadata_state.spawn_git_enrichment_task(); // TODO(ccunningham): Review turns currently rely on `spawn_task` for TurnComplete but do not diff --git a/codex-rs/core/src/session/tests.rs b/codex-rs/core/src/session/tests.rs index 14bf1f6088f..d742a7f8828 100644 --- a/codex-rs/core/src/session/tests.rs +++ b/codex-rs/core/src/session/tests.rs @@ -2367,7 +2367,6 @@ async fn fork_startup_context_then_first_turn_diff_snapshot() -> anyhow::Result< .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "fork seed".into(), text_elements: Vec::new(), }], @@ -2415,7 +2414,6 @@ async fn fork_startup_context_then_first_turn_diff_snapshot() -> anyhow::Result< .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "after fork".into(), text_elements: Vec::new(), }], @@ -5412,6 +5410,7 @@ async fn submit_with_id_captures_current_span_trace_context() { .submit_with_id(Submission { id: "sub-1".into(), op: Op::Interrupt, + client_user_message_id: None, trace: None, }) .await @@ -5483,6 +5482,7 @@ fn submission_dispatch_span_prefers_submission_trace_context() { submission_dispatch_span(&Submission { id: "sub-1".into(), op: Op::Interrupt, + client_user_message_id: None, trace: Some(submission_trace), }) }); @@ -5509,6 +5509,7 @@ fn submission_dispatch_span_uses_debug_for_realtime_audio() { item_id: None, }, }), + client_user_message_id: None, trace: None, }); @@ -5551,7 +5552,6 @@ async fn user_turn_updates_approvals_reviewer() { "sub-1".to_string(), Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: "hello".to_string(), text_elements: Vec::new(), }], @@ -5870,6 +5870,7 @@ async fn spawn_task_turn_span_inherits_dispatch_trace_context() { let dispatch_span = submission_dispatch_span(&Submission { id: "sub-1".into(), op: Op::Interrupt, + client_user_message_id: None, trace: Some(submission_trace.clone()), }); let dispatch_span_id = dispatch_span.context().span().span_context().span_id(); @@ -5880,11 +5881,13 @@ async fn spawn_task_turn_span_inherits_dispatch_trace_context() { async { sess.spawn_task( Arc::clone(&tc), - vec![TurnInput::UserInput(vec![UserInput::Text { + vec![TurnInput::UserInput { + content: vec![UserInput::Text { + text: "hello".to_string(), + text_elements: Vec::new(), + }], client_id: None, - text: "hello".to_string(), - text_elements: Vec::new(), - }])], + }], TraceCaptureTask { captured_trace: Arc::clone(&captured_trace), }, @@ -6693,11 +6696,13 @@ async fn spawn_task_does_not_update_previous_turn_settings_for_non_run_turn_task let (sess, tc, _rx) = make_session_and_context_with_rx().await; sess.set_previous_turn_settings(/*previous_turn_settings*/ None) .await; - let input = vec![TurnInput::UserInput(vec![UserInput::Text { + let input = vec![TurnInput::UserInput { + content: vec![UserInput::Text { + text: "hello".to_string(), + text_elements: Vec::new(), + }], client_id: None, - text: "hello".to_string(), - text_elements: Vec::new(), - }])]; + }]; sess.spawn_task( Arc::clone(&tc), @@ -7963,11 +7968,13 @@ impl SessionTask for GuardianDeniedApprovalTask { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn guardian_auto_review_interrupts_after_three_consecutive_denials() { let (sess, tc, rx) = make_session_and_context_with_rx().await; - let input = vec![TurnInput::UserInput(vec![UserInput::Text { + let input = vec![TurnInput::UserInput { + content: vec![UserInput::Text { + text: "trigger guardian denials".to_string(), + text_elements: Vec::new(), + }], client_id: None, - text: "trigger guardian denials".to_string(), - text_elements: Vec::new(), - }])]; + }]; sess.spawn_task(Arc::clone(&tc), input, GuardianDeniedApprovalTask) .await; @@ -7995,11 +8002,13 @@ async fn guardian_auto_review_interrupts_after_three_consecutive_denials() { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn guardian_helper_review_interrupts_after_three_consecutive_denials() { let (sess, tc, rx) = make_session_and_context_with_rx().await; - let input = vec![TurnInput::UserInput(vec![UserInput::Text { + let input = vec![TurnInput::UserInput { + content: vec![UserInput::Text { + text: "keep turn active for helper reviews".to_string(), + text_elements: Vec::new(), + }], client_id: None, - text: "keep turn active for helper reviews".to_string(), - text_elements: Vec::new(), - }])]; + }]; sess.spawn_task( Arc::clone(&tc), input, @@ -8056,11 +8065,13 @@ async fn guardian_helper_review_interrupts_after_three_consecutive_denials() { #[test_log::test] async fn abort_regular_task_emits_marker_before_turn_aborted() { let (sess, tc, rx) = make_session_and_context_with_rx().await; - let input = vec![TurnInput::UserInput(vec![UserInput::Text { + let input = vec![TurnInput::UserInput { + content: vec![UserInput::Text { + text: "hello".to_string(), + text_elements: Vec::new(), + }], client_id: None, - text: "hello".to_string(), - text_elements: Vec::new(), - }])]; + }]; sess.spawn_task( Arc::clone(&tc), input, @@ -8095,11 +8106,13 @@ async fn abort_regular_task_emits_marker_before_turn_aborted() { #[tokio::test] async fn abort_gracefully_emits_marker_before_turn_aborted() { let (sess, tc, rx) = make_session_and_context_with_rx().await; - let input = vec![TurnInput::UserInput(vec![UserInput::Text { + let input = vec![TurnInput::UserInput { + content: vec![UserInput::Text { + text: "hello".to_string(), + text_elements: Vec::new(), + }], client_id: None, - text: "hello".to_string(), - text_elements: Vec::new(), - }])]; + }]; sess.spawn_task( Arc::clone(&tc), input, @@ -8134,11 +8147,13 @@ async fn abort_gracefully_emits_marker_before_turn_aborted() { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn task_finish_emits_turn_item_lifecycle_for_leftover_pending_user_input() { let (sess, tc, rx) = make_session_and_context_with_rx().await; - let input = vec![TurnInput::UserInput(vec![UserInput::Text { + let input = vec![TurnInput::UserInput { + content: vec![UserInput::Text { + text: "hello".to_string(), + text_elements: Vec::new(), + }], client_id: None, - text: "hello".to_string(), - text_elements: Vec::new(), - }])]; + }]; sess.spawn_task( Arc::clone(&tc), input, @@ -8156,7 +8171,6 @@ async fn task_finish_emits_turn_item_lifecycle_for_leftover_pending_user_input() Some("pending marker".to_string()), ); let pending_user_input = vec![UserInput::Text { - client_id: None, text: "late pending input".to_string(), text_elements: vec![text_element.clone()], }]; @@ -8164,6 +8178,7 @@ async fn task_finish_emits_turn_item_lifecycle_for_leftover_pending_user_input() pending_user_input.clone(), /*additional_context*/ Default::default(), Some(&tc.sub_id), + /*client_user_message_id*/ None, /*responsesapi_client_metadata*/ None, ) .await @@ -8253,7 +8268,6 @@ async fn task_finish_emits_turn_item_lifecycle_for_leftover_pending_user_input() async fn steer_input_requires_active_turn() { let (sess, _tc, _rx) = make_session_and_context_with_rx().await; let input = vec![UserInput::Text { - client_id: None, text: "steer".to_string(), text_elements: Vec::new(), }]; @@ -8263,6 +8277,7 @@ async fn steer_input_requires_active_turn() { input, /*additional_context*/ Default::default(), /*expected_turn_id*/ None, + /*client_user_message_id*/ None, /*responsesapi_client_metadata*/ None, ) .await @@ -8274,11 +8289,13 @@ async fn steer_input_requires_active_turn() { #[tokio::test] async fn steer_input_enforces_expected_turn_id() { let (sess, tc, _rx) = make_session_and_context_with_rx().await; - let input = vec![TurnInput::UserInput(vec![UserInput::Text { + let input = vec![TurnInput::UserInput { + content: vec![UserInput::Text { + text: "hello".to_string(), + text_elements: Vec::new(), + }], client_id: None, - text: "hello".to_string(), - text_elements: Vec::new(), - }])]; + }]; sess.spawn_task( Arc::clone(&tc), input, @@ -8290,7 +8307,6 @@ async fn steer_input_enforces_expected_turn_id() { .await; let steer_input = vec![UserInput::Text { - client_id: None, text: "steer".to_string(), text_elements: Vec::new(), }]; @@ -8299,6 +8315,7 @@ async fn steer_input_enforces_expected_turn_id() { steer_input, /*additional_context*/ Default::default(), Some("different-turn-id"), + /*client_user_message_id*/ None, /*responsesapi_client_metadata*/ None, ) .await @@ -8322,11 +8339,13 @@ async fn steer_input_rejects_non_regular_turns() { (TaskKind::Compact, NonSteerableTurnKind::Compact), ] { let (sess, _tc, _rx) = make_session_and_context_with_rx().await; - let input = vec![TurnInput::UserInput(vec![UserInput::Text { + let input = vec![TurnInput::UserInput { + content: vec![UserInput::Text { + text: "hello".to_string(), + text_elements: Vec::new(), + }], client_id: None, - text: "hello".to_string(), - text_elements: Vec::new(), - }])]; + }]; let turn_context = sess.new_default_turn_with_sub_id("turn".to_string()).await; sess.spawn_task( turn_context, @@ -8339,7 +8358,6 @@ async fn steer_input_rejects_non_regular_turns() { .await; let steer_input = vec![UserInput::Text { - client_id: None, text: "steer".to_string(), text_elements: Vec::new(), }]; @@ -8348,6 +8366,7 @@ async fn steer_input_rejects_non_regular_turns() { steer_input, /*additional_context*/ Default::default(), /*expected_turn_id*/ None, + /*client_user_message_id*/ None, /*responsesapi_client_metadata*/ None, ) .await @@ -8362,11 +8381,13 @@ async fn steer_input_rejects_non_regular_turns() { #[tokio::test] async fn steer_input_returns_active_turn_id() { let (sess, tc, _rx) = make_session_and_context_with_rx().await; - let input = vec![TurnInput::UserInput(vec![UserInput::Text { + let input = vec![TurnInput::UserInput { + content: vec![UserInput::Text { + text: "hello".to_string(), + text_elements: Vec::new(), + }], client_id: None, - text: "hello".to_string(), - text_elements: Vec::new(), - }])]; + }]; sess.spawn_task( Arc::clone(&tc), input, @@ -8378,7 +8399,6 @@ async fn steer_input_returns_active_turn_id() { .await; let steer_input = vec![UserInput::Text { - client_id: None, text: "steer".to_string(), text_elements: Vec::new(), }]; @@ -8387,6 +8407,7 @@ async fn steer_input_returns_active_turn_id() { steer_input, /*additional_context*/ Default::default(), Some(&tc.sub_id), + /*client_user_message_id*/ None, /*responsesapi_client_metadata*/ None, ) .await @@ -8550,7 +8571,6 @@ async fn active_goal_continuation_runs_again_after_no_tool_turn() -> anyhow::Res .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "write a benchmark note".into(), text_elements: Vec::new(), }], @@ -8657,7 +8677,6 @@ async fn pending_request_user_input_does_not_spawn_extra_goal_continuation() -> .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "write a benchmark note".into(), text_elements: Vec::new(), }], @@ -9206,7 +9225,6 @@ async fn completed_goal_accounts_current_turn_tokens_before_tool_response() -> a .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "write a report".into(), text_elements: Vec::new(), }], @@ -9376,12 +9394,12 @@ async fn steered_input_reopens_mailbox_delivery_for_current_turn() { .await; sess.steer_input( vec![UserInput::Text { - client_id: None, text: "follow up".to_string(), text_elements: Vec::new(), }], /*additional_context*/ Default::default(), Some(&tc.sub_id), + /*client_user_message_id*/ None, /*responsesapi_client_metadata*/ None, ) .await @@ -9390,11 +9408,13 @@ async fn steered_input_reopens_mailbox_delivery_for_current_turn() { assert_eq!( sess.input_queue.get_pending_input(&sess.active_turn).await, vec![ - TurnInput::UserInput(vec![UserInput::Text { - client_id: None, - text: "follow up".to_string(), - text_elements: Vec::new(), - }]), + TurnInput::UserInput { + content: vec![UserInput::Text { + text: "follow up".to_string(), + text_elements: Vec::new(), + }], + client_id: None + }, TurnInput::ResponseItem(ResponseItem::from(communication.to_response_input_item())), ], ); @@ -9428,12 +9448,12 @@ async fn stale_defer_mailbox_delivery_does_not_override_steered_input() { .await; sess.steer_input( vec![UserInput::Text { - client_id: None, text: "follow up".to_string(), text_elements: Vec::new(), }], /*additional_context*/ Default::default(), Some(&tc.sub_id), + /*client_user_message_id*/ None, /*responsesapi_client_metadata*/ None, ) .await @@ -9446,11 +9466,13 @@ async fn stale_defer_mailbox_delivery_does_not_override_steered_input() { assert_eq!( sess.input_queue.get_pending_input(&sess.active_turn).await, vec![ - TurnInput::UserInput(vec![UserInput::Text { - client_id: None, - text: "follow up".to_string(), - text_elements: Vec::new(), - }]), + TurnInput::UserInput { + content: vec![UserInput::Text { + text: "follow up".to_string(), + text_elements: Vec::new(), + }], + client_id: None + }, TurnInput::ResponseItem(ResponseItem::from(communication.to_response_input_item())), ], ); @@ -9515,11 +9537,13 @@ async fn tool_calls_reopen_mailbox_delivery_for_current_turn() { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn abort_review_task_emits_exited_then_aborted_and_records_history() { let (sess, tc, rx) = make_session_and_context_with_rx().await; - let input = vec![TurnInput::UserInput(vec![UserInput::Text { + let input = vec![TurnInput::UserInput { + content: vec![UserInput::Text { + text: "start review".to_string(), + text_elements: Vec::new(), + }], client_id: None, - text: "start review".to_string(), - text_elements: Vec::new(), - }])]; + }]; sess.spawn_task(Arc::clone(&tc), input, ReviewTask::new()) .await; diff --git a/codex-rs/core/src/session/turn.rs b/codex-rs/core/src/session/turn.rs index 477e38c81b7..8219c190196 100644 --- a/codex-rs/core/src/session/turn.rs +++ b/codex-rs/core/src/session/turn.rs @@ -429,7 +429,7 @@ async fn run_hooks_and_record_inputs( blocked_input = true; record_additional_contexts(sess, turn_context, hook_outcome.additional_contexts).await; } else { - if matches!(input_item, TurnInput::UserInput(items) if !items.is_empty()) { + if matches!(input_item, TurnInput::UserInput { content, .. } if !content.is_empty()) { accepted_user_input = true; } record_pending_input( @@ -457,7 +457,7 @@ async fn build_skills_and_plugins( let user_input = input .iter() .filter_map(|item| match item { - TurnInput::UserInput(content) => Some(content.as_slice()), + TurnInput::UserInput { content, .. } => Some(content.as_slice()), TurnInput::ResponseItem(_) => None, }) .flatten() @@ -610,7 +610,7 @@ async fn track_turn_resolved_config_analytics( num_input_images: input .iter() .filter_map(|item| match item { - TurnInput::UserInput(content) => Some(content.as_slice()), + TurnInput::UserInput { content, .. } => Some(content.as_slice()), TurnInput::ResponseItem(_) => None, }) .flatten() diff --git a/codex-rs/core/src/tasks/compact.rs b/codex-rs/core/src/tasks/compact.rs index 03a23e5c774..adc98e413a8 100644 --- a/codex-rs/core/src/tasks/compact.rs +++ b/codex-rs/core/src/tasks/compact.rs @@ -55,7 +55,6 @@ impl SessionTask for CompactTask { /*manual*/ true, ); let input = vec![UserInput::Text { - client_id: None, text: ctx.compact_prompt().to_string(), // Compaction prompt is synthesized; no UI element ranges to preserve. text_elements: Vec::new(), diff --git a/codex-rs/core/src/tasks/review.rs b/codex-rs/core/src/tasks/review.rs index 514bb13e4cf..3ed8677b3c4 100644 --- a/codex-rs/core/src/tasks/review.rs +++ b/codex-rs/core/src/tasks/review.rs @@ -72,7 +72,7 @@ impl SessionTask for ReviewTask { let mut user_input = Vec::new(); for item in input { match item { - TurnInput::UserInput(mut content) => user_input.append(&mut content), + TurnInput::UserInput { mut content, .. } => user_input.append(&mut content), TurnInput::ResponseItem(_) => {} } } diff --git a/codex-rs/core/src/tools/handlers/agent_jobs.rs b/codex-rs/core/src/tools/handlers/agent_jobs.rs index 789a8ffd3a9..4c3cd34c2ea 100644 --- a/codex-rs/core/src/tools/handlers/agent_jobs.rs +++ b/codex-rs/core/src/tools/handlers/agent_jobs.rs @@ -198,7 +198,6 @@ async fn run_agent_job_loop( for item in pending_items { let prompt = build_worker_prompt(&job, &item)?; let items = vec![UserInput::Text { - client_id: None, text: prompt, text_elements: Vec::new(), }]; diff --git a/codex-rs/core/src/tools/handlers/multi_agents_common.rs b/codex-rs/core/src/tools/handlers/multi_agents_common.rs index 117dd6225ea..f1e7bcd429b 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents_common.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents_common.rs @@ -179,7 +179,6 @@ pub(crate) fn parse_collab_input( )); } Ok(vec![UserInput::Text { - client_id: None, text: message, text_elements: Vec::new(), }] diff --git a/codex-rs/core/src/tools/handlers/multi_agents_tests.rs b/codex-rs/core/src/tools/handlers/multi_agents_tests.rs index aa4904465ce..164d5410e0b 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents_tests.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents_tests.rs @@ -1360,7 +1360,6 @@ async fn multi_agent_v2_send_message_accepts_root_target_from_child() { .spawn_agent_with_metadata( (*turn.config).clone(), vec![UserInput::Text { - client_id: None, text: "inspect this repo".to_string(), text_elements: Vec::new(), }] @@ -1437,7 +1436,6 @@ async fn multi_agent_v2_followup_task_rejects_root_target_from_child() { .spawn_agent_with_metadata( (*turn.config).clone(), vec![UserInput::Text { - client_id: None, text: "inspect this repo".to_string(), text_elements: Vec::new(), }] @@ -1611,7 +1609,6 @@ async fn multi_agent_v2_list_agents_filters_by_relative_path_prefix() { .spawn_agent_with_metadata( config.clone(), vec![UserInput::Text { - client_id: None, text: "research".to_string(), text_elements: Vec::new(), }] @@ -1633,7 +1630,6 @@ async fn multi_agent_v2_list_agents_filters_by_relative_path_prefix() { .spawn_agent_with_metadata( config, vec![UserInput::Text { - client_id: None, text: "build".to_string(), text_elements: Vec::new(), }] @@ -2587,12 +2583,10 @@ async fn send_input_accepts_structured_items() { environments: None, items: vec![ UserInput::Mention { - client_id: None, name: "drive".to_string(), path: "app://google_drive".to_string(), }, UserInput::Text { - client_id: None, text: "read the folder".to_string(), text_elements: Vec::new(), }, diff --git a/codex-rs/core/tests/common/test_codex.rs b/codex-rs/core/tests/common/test_codex.rs index c5d762a667e..73e9e0ece88 100644 --- a/codex-rs/core/tests/common/test_codex.rs +++ b/codex-rs/core/tests/common/test_codex.rs @@ -761,7 +761,6 @@ impl TestCodex { self.codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: prompt.into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/abort_tasks.rs b/codex-rs/core/tests/suite/abort_tasks.rs index 94aaad314fc..82bb8c87987 100644 --- a/codex-rs/core/tests/suite/abort_tasks.rs +++ b/codex-rs/core/tests/suite/abort_tasks.rs @@ -48,7 +48,6 @@ async fn interrupt_long_running_tool_emits_turn_aborted() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "start sleep".into(), text_elements: Vec::new(), }], @@ -107,7 +106,6 @@ async fn interrupt_tool_records_history_entries() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "start history recording".into(), text_elements: Vec::new(), }], @@ -130,7 +128,6 @@ async fn interrupt_tool_records_history_entries() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "follow up".into(), text_elements: Vec::new(), }], @@ -215,7 +212,6 @@ async fn interrupt_persists_turn_aborted_marker_in_next_request() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "start interrupt marker".into(), text_elements: Vec::new(), }], @@ -238,7 +234,6 @@ async fn interrupt_persists_turn_aborted_marker_in_next_request() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "follow up".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/additional_context.rs b/codex-rs/core/tests/suite/additional_context.rs index 374e727f419..f9bfdb966b6 100644 --- a/codex-rs/core/tests/suite/additional_context.rs +++ b/codex-rs/core/tests/suite/additional_context.rs @@ -39,7 +39,6 @@ async fn additional_context_is_model_visible_but_not_a_user_message_item() -> Re .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "inspect the active tab".to_string(), text_elements: Vec::new(), }], @@ -76,7 +75,6 @@ async fn additional_context_is_model_visible_but_not_a_user_message_item() -> Re assert_eq!( user_item.content, vec![UserInput::Text { - client_id: None, text: "inspect the active tab".to_string(), text_elements: Vec::new(), }] @@ -132,7 +130,6 @@ async fn external_context_like_user_text_remains_a_user_message_item() -> Result .build(&server) .await?; let user_input = UserInput::Text { - client_id: None, text: "".to_string(), text_elements: Vec::new(), }; @@ -187,7 +184,6 @@ async fn additional_context_trust_controls_message_role() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "inspect context".to_string(), text_elements: Vec::new(), }], @@ -269,7 +265,6 @@ async fn additional_context_is_deduplicated_between_turns_while_retained() -> Re .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "first turn".to_string(), text_elements: Vec::new(), }], @@ -288,7 +283,6 @@ async fn additional_context_is_deduplicated_between_turns_while_retained() -> Re .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "second turn".to_string(), text_elements: Vec::new(), }], @@ -351,7 +345,6 @@ async fn additional_context_removes_one_value_while_adding_another() -> Result<( .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "first turn".to_string(), text_elements: Vec::new(), }], @@ -385,7 +378,6 @@ async fn additional_context_removes_one_value_while_adding_another() -> Result<( .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "second turn".to_string(), text_elements: Vec::new(), }], @@ -419,7 +411,6 @@ async fn additional_context_removes_one_value_while_adding_another() -> Result<( .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "third turn".to_string(), text_elements: Vec::new(), }], @@ -517,7 +508,6 @@ async fn additional_context_values_are_truncated_before_model_input() -> Result< .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "summarize context".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/apply_patch_cli.rs b/codex-rs/core/tests/suite/apply_patch_cli.rs index 8835fa639e2..da138851f55 100644 --- a/codex-rs/core/tests/suite/apply_patch_cli.rs +++ b/codex-rs/core/tests/suite/apply_patch_cli.rs @@ -85,7 +85,6 @@ async fn submit_without_wait_with_turn_permissions( test.codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: prompt.into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/approvals.rs b/codex-rs/core/tests/suite/approvals.rs index c0061058162..e16a4a14a20 100644 --- a/codex-rs/core/tests/suite/approvals.rs +++ b/codex-rs/core/tests/suite/approvals.rs @@ -647,7 +647,6 @@ async fn submit_turn( test.codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: prompt.into(), text_elements: Vec::new(), }], @@ -2594,7 +2593,6 @@ async fn matched_prefix_rule_runs_unsandboxed_under_zsh_fork() -> Result<()> { test.codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: "run allowed touch under zsh fork".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/client.rs b/codex-rs/core/tests/suite/client.rs index d6449dc85fd..7234c59c0fd 100644 --- a/codex-rs/core/tests/suite/client.rs +++ b/codex-rs/core/tests/suite/client.rs @@ -387,7 +387,6 @@ async fn resume_includes_initial_messages_and_sends_prior_items() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -755,7 +754,6 @@ async fn includes_session_id_thread_id_and_model_headers_in_request() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -968,7 +966,6 @@ async fn includes_base_instructions_override_in_request() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -1027,7 +1024,6 @@ async fn chatgpt_auth_sends_correct_request() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -1153,7 +1149,6 @@ async fn prefers_apikey_when_config_prefers_apikey_even_with_chatgpt_tokens() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -1194,7 +1189,6 @@ async fn includes_user_instructions_message_in_request() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -1284,7 +1278,6 @@ async fn includes_apps_guidance_as_developer_message_for_chatgpt_auth() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -1349,7 +1342,6 @@ async fn omits_apps_guidance_for_api_key_auth_even_when_feature_enabled() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -1410,7 +1402,6 @@ async fn omits_apps_guidance_when_configured_off() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -1454,7 +1445,6 @@ async fn omits_environment_context_when_configured_off() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -1513,7 +1503,6 @@ async fn skills_append_to_developer_message() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -1598,7 +1587,6 @@ async fn skills_use_aliases_in_developer_message_under_budget_pressure() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -1661,7 +1649,6 @@ async fn includes_configured_effort_in_request() -> anyhow::Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -1705,7 +1692,6 @@ async fn includes_no_effort_in_request() -> anyhow::Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -1750,7 +1736,6 @@ async fn includes_default_reasoning_effort_in_request_when_defined_by_model_info .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -1802,7 +1787,6 @@ async fn user_turn_collaboration_mode_overrides_model_and_effort() -> anyhow::Re codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -1861,7 +1845,6 @@ async fn configured_reasoning_summary_is_sent() -> anyhow::Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -1927,7 +1910,6 @@ async fn user_turn_explicit_reasoning_summary_overrides_model_catalog_default() codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -1990,7 +1972,6 @@ async fn reasoning_summary_is_omitted_when_disabled() -> anyhow::Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -2051,7 +2032,6 @@ async fn reasoning_summary_none_overrides_model_catalog_default() -> anyhow::Res .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -2092,7 +2072,6 @@ async fn includes_default_verbosity_in_request() -> anyhow::Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -2142,7 +2121,6 @@ async fn configured_verbosity_not_sent_for_models_without_support() -> anyhow::R .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -2191,7 +2169,6 @@ async fn configured_verbosity_is_sent() -> anyhow::Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -2245,7 +2222,6 @@ async fn includes_developer_instructions_message_in_request() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -2543,7 +2519,6 @@ async fn token_count_includes_rate_limits_snapshot() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -2684,7 +2659,6 @@ async fn usage_limit_error_emits_rate_limit_event() -> anyhow::Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -2763,7 +2737,6 @@ async fn context_window_error_sets_total_tokens_to_model_window() -> anyhow::Res .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "seed turn".into(), text_elements: Vec::new(), }], @@ -2780,7 +2753,6 @@ async fn context_window_error_sets_total_tokens_to_model_window() -> anyhow::Res .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "trigger context window".into(), text_elements: Vec::new(), }], @@ -2867,7 +2839,6 @@ async fn incomplete_response_emits_content_filter_error_message() -> anyhow::Res .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "trigger incomplete".into(), text_elements: Vec::new(), }], @@ -2980,7 +2951,6 @@ async fn azure_overrides_assign_properties_used_for_responses_url() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -3071,7 +3041,6 @@ async fn env_var_overrides_loaded_auth() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -3130,7 +3099,6 @@ async fn history_dedupes_streamed_and_final_messages_across_turns() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "U1".into(), text_elements: Vec::new(), }], @@ -3148,7 +3116,6 @@ async fn history_dedupes_streamed_and_final_messages_across_turns() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "U2".into(), text_elements: Vec::new(), }], @@ -3166,7 +3133,6 @@ async fn history_dedupes_streamed_and_final_messages_across_turns() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "U3".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/client_websockets.rs b/codex-rs/core/tests/suite/client_websockets.rs index 5d7721615a6..09d0093f990 100755 --- a/codex-rs/core/tests/suite/client_websockets.rs +++ b/codex-rs/core/tests/suite/client_websockets.rs @@ -1314,7 +1314,6 @@ async fn responses_websocket_usage_limit_error_emits_rate_limit_event() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -1405,7 +1404,6 @@ async fn responses_websocket_invalid_request_error_with_status_is_forwarded() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/code_mode.rs b/codex-rs/core/tests/suite/code_mode.rs index b77ff9963a0..90daf219632 100644 --- a/codex-rs/core/tests/suite/code_mode.rs +++ b/codex-rs/core/tests/suite/code_mode.rs @@ -2985,7 +2985,6 @@ text( test.codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: "use exec to inspect and call hidden tools".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/collaboration_instructions.rs b/codex-rs/core/tests/suite/collaboration_instructions.rs index 88512bed52c..bbbb0f1aa23 100644 --- a/codex-rs/core/tests/suite/collaboration_instructions.rs +++ b/codex-rs/core/tests/suite/collaboration_instructions.rs @@ -74,7 +74,6 @@ async fn no_collaboration_instructions_by_default() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -130,7 +129,6 @@ async fn user_input_includes_collaboration_instructions_after_override() -> Resu .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -168,7 +166,6 @@ async fn collaboration_instructions_added_on_user_turn() -> Result<()> { test.codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -220,7 +217,6 @@ async fn collaboration_instructions_omitted_when_disabled() -> Result<()> { test.codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -282,7 +278,6 @@ async fn override_then_next_turn_uses_updated_collaboration_instructions() -> Re .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -331,7 +326,6 @@ async fn user_turn_overrides_collaboration_instructions_after_override() -> Resu test.codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -398,7 +392,6 @@ async fn collaboration_mode_update_emits_new_instruction_message() -> Result<()> .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello 1".into(), text_elements: Vec::new(), }], @@ -423,7 +416,6 @@ async fn collaboration_mode_update_emits_new_instruction_message() -> Result<()> .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello 2".into(), text_elements: Vec::new(), }], @@ -477,7 +469,6 @@ async fn collaboration_mode_update_noop_does_not_append() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello 1".into(), text_elements: Vec::new(), }], @@ -502,7 +493,6 @@ async fn collaboration_mode_update_noop_does_not_append() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello 2".into(), text_elements: Vec::new(), }], @@ -558,7 +548,6 @@ async fn collaboration_mode_update_emits_new_instruction_message_when_mode_chang .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello 1".into(), text_elements: Vec::new(), }], @@ -586,7 +575,6 @@ async fn collaboration_mode_update_emits_new_instruction_message_when_mode_chang .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello 2".into(), text_elements: Vec::new(), }], @@ -643,7 +631,6 @@ async fn collaboration_mode_update_noop_does_not_append_when_mode_is_unchanged() .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello 1".into(), text_elements: Vec::new(), }], @@ -671,7 +658,6 @@ async fn collaboration_mode_update_noop_does_not_append_when_mode_is_unchanged() .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello 2".into(), text_elements: Vec::new(), }], @@ -731,7 +717,6 @@ async fn resume_replays_collaboration_instructions() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -749,7 +734,6 @@ async fn resume_replays_collaboration_instructions() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "after resume".into(), text_elements: Vec::new(), }], @@ -803,7 +787,6 @@ async fn empty_collaboration_instructions_are_ignored() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/compact.rs b/codex-rs/core/tests/suite/compact.rs index 197434d2e34..0e2c02de917 100644 --- a/codex-rs/core/tests/suite/compact.rs +++ b/codex-rs/core/tests/suite/compact.rs @@ -91,7 +91,6 @@ fn disabled_permission_user_turn(text: impl Into, cwd: PathBuf, model: S turn_permission_fields(PermissionProfile::Disabled, cwd.as_path()); Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: text.into(), text_elements: Vec::new(), }], @@ -405,7 +404,6 @@ async fn summarize_context_three_requests_and_instructions() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello world".into(), text_elements: Vec::new(), }], @@ -432,7 +430,6 @@ async fn summarize_context_three_requests_and_instructions() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: THIRD_USER_MSG.into(), text_elements: Vec::new(), }], @@ -609,7 +606,6 @@ async fn manual_pre_compact_block_decision_does_not_block_compaction() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello before blocked compact".to_string(), text_elements: Vec::new(), }], @@ -684,7 +680,6 @@ async fn compact_hooks_respect_matchers_and_post_runs_after_compaction() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello before matched compact".to_string(), text_elements: Vec::new(), }], @@ -756,7 +751,6 @@ async fn manual_compact_uses_custom_prompt() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "USER_ONE".to_string(), text_elements: Vec::new(), }], @@ -905,7 +899,6 @@ async fn manual_compact_emits_context_compaction_items() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "manual compact".into(), text_elements: Vec::new(), }], @@ -1073,7 +1066,6 @@ async fn multiple_auto_compact_per_task_runs_after_token_limit_hit() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: user_message.into(), text_elements: Vec::new(), }], @@ -1547,7 +1539,6 @@ async fn auto_compact_runs_after_token_limit_hit() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: FIRST_AUTO_MSG.into(), text_elements: Vec::new(), }], @@ -1565,7 +1556,6 @@ async fn auto_compact_runs_after_token_limit_hit() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: SECOND_AUTO_MSG.into(), text_elements: Vec::new(), }], @@ -1583,7 +1573,6 @@ async fn auto_compact_runs_after_token_limit_hit() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: POST_AUTO_USER_MSG.into(), text_elements: Vec::new(), }], @@ -1756,7 +1745,6 @@ async fn auto_compact_emits_context_compaction_items() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: user.into(), text_elements: Vec::new(), }], @@ -1839,7 +1827,6 @@ async fn auto_compact_starts_after_turn_started() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: FIRST_AUTO_MSG.into(), text_elements: Vec::new(), }], @@ -1856,7 +1843,6 @@ async fn auto_compact_starts_after_turn_started() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: SECOND_AUTO_MSG.into(), text_elements: Vec::new(), }], @@ -1873,7 +1859,6 @@ async fn auto_compact_starts_after_turn_started() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: POST_AUTO_USER_MSG.into(), text_elements: Vec::new(), }], @@ -2403,7 +2388,6 @@ async fn auto_compact_persists_rollout_entries() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: FIRST_AUTO_MSG.into(), text_elements: Vec::new(), }], @@ -2420,7 +2404,6 @@ async fn auto_compact_persists_rollout_entries() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: SECOND_AUTO_MSG.into(), text_elements: Vec::new(), }], @@ -2437,7 +2420,6 @@ async fn auto_compact_persists_rollout_entries() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: POST_AUTO_USER_MSG.into(), text_elements: Vec::new(), }], @@ -2528,7 +2510,6 @@ async fn manual_compact_retries_after_context_window_error() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "first turn".into(), text_elements: Vec::new(), }], @@ -2634,7 +2615,6 @@ async fn manual_compact_non_context_failure_retries_then_emits_task_error() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "first turn".into(), text_elements: Vec::new(), }], @@ -2731,7 +2711,6 @@ async fn manual_compact_twice_preserves_latest_user_messages() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: first_user_message.into(), text_elements: Vec::new(), }], @@ -2751,7 +2730,6 @@ async fn manual_compact_twice_preserves_latest_user_messages() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: second_user_message.into(), text_elements: Vec::new(), }], @@ -2771,7 +2749,6 @@ async fn manual_compact_twice_preserves_latest_user_messages() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: final_user_message.into(), text_elements: Vec::new(), }], @@ -2984,7 +2961,6 @@ async fn auto_compact_allows_multiple_attempts_when_interleaved_with_other_turn_ .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: user.into(), text_elements: Vec::new(), }], @@ -3091,7 +3067,6 @@ async fn snapshot_request_shape_mid_turn_continuation_compaction() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: FUNCTION_CALL_LIMIT_MSG.into(), text_elements: Vec::new(), }], @@ -3524,7 +3499,6 @@ async fn auto_compact_counts_encrypted_reasoning_before_last_user() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: user.into(), text_elements: Vec::new(), }], @@ -3645,7 +3619,6 @@ async fn auto_compact_runs_when_reasoning_header_clears_between_turns() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: user.into(), text_elements: Vec::new(), }], @@ -3709,7 +3682,6 @@ async fn snapshot_request_shape_pre_turn_compaction_including_incoming_user_mess .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: user.to_string(), text_elements: Vec::new(), }], @@ -3738,12 +3710,10 @@ async fn snapshot_request_shape_pre_turn_compaction_including_incoming_user_mess environments: None, items: vec![ UserInput::Image { - client_id: None, image_url: image_url.clone(), detail: None, }, UserInput::Text { - client_id: None, text: "USER_THREE".to_string(), text_elements: Vec::new(), }, @@ -3935,7 +3905,6 @@ async fn snapshot_request_shape_pre_turn_compaction_context_window_exceeded() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "USER_ONE".to_string(), text_elements: Vec::new(), }], @@ -3952,7 +3921,6 @@ async fn snapshot_request_shape_pre_turn_compaction_context_window_exceeded() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "USER_TWO".to_string(), text_elements: Vec::new(), }], @@ -4027,7 +3995,6 @@ async fn snapshot_request_shape_manual_compact_without_previous_user_messages() .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "AFTER_MANUAL_EMPTY_COMPACT".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/compact_remote.rs b/codex-rs/core/tests/suite/compact_remote.rs index c62f7143ec1..1c5b80f35f9 100644 --- a/codex-rs/core/tests/suite/compact_remote.rs +++ b/codex-rs/core/tests/suite/compact_remote.rs @@ -321,7 +321,6 @@ async fn remote_compact_replaces_history_for_followups() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello remote compact".into(), text_elements: Vec::new(), }], @@ -340,7 +339,6 @@ async fn remote_compact_replaces_history_for_followups() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "after compact".into(), text_elements: Vec::new(), }], @@ -584,7 +582,6 @@ async fn assert_remote_manual_compact_request_parity( .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "TURN_ONE_USER".to_string(), text_elements: Vec::new(), }], @@ -601,12 +598,10 @@ async fn assert_remote_manual_compact_request_parity( environments: None, items: vec![ UserInput::Text { - client_id: None, text: "TURN_TWO_PREFIX".to_string(), text_elements: Vec::new(), }, UserInput::Text { - client_id: None, text: "TURN_TWO_SUFFIX".to_string(), text_elements: Vec::new(), }, @@ -623,7 +618,6 @@ async fn assert_remote_manual_compact_request_parity( .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "TURN_THREE_TOOL_USER".to_string(), text_elements: Vec::new(), }], @@ -640,12 +634,10 @@ async fn assert_remote_manual_compact_request_parity( environments: None, items: vec![ UserInput::Image { - client_id: None, image_url, detail: None, }, UserInput::Text { - client_id: None, text: "TURN_FOUR_IMAGE_USER".to_string(), text_elements: Vec::new(), }, @@ -662,7 +654,6 @@ async fn assert_remote_manual_compact_request_parity( .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "TURN_FIVE_USER".to_string(), text_elements: Vec::new(), }], @@ -832,7 +823,6 @@ async fn remote_compact_v2_reuses_compaction_trigger_for_followups() -> Result<( .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello remote compact".into(), text_elements: Vec::new(), }], @@ -851,7 +841,6 @@ async fn remote_compact_v2_reuses_compaction_trigger_for_followups() -> Result<( .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "after compact".into(), text_elements: Vec::new(), }], @@ -980,7 +969,6 @@ async fn remote_compact_v2_retries_failures_with_stream_retry_budget() -> Result .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello remote compact".into(), text_elements: Vec::new(), }], @@ -999,7 +987,6 @@ async fn remote_compact_v2_retries_failures_with_stream_retry_budget() -> Result .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "after compact".into(), text_elements: Vec::new(), }], @@ -1087,7 +1074,6 @@ async fn remote_compact_v2_accepts_additional_output_items_before_compaction() - .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello remote compact".into(), text_elements: Vec::new(), }], @@ -1106,7 +1092,6 @@ async fn remote_compact_v2_accepts_additional_output_items_before_compaction() - .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "after compact".into(), text_elements: Vec::new(), }], @@ -1199,7 +1184,6 @@ async fn remote_compact_filters_deferred_dynamic_tools() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello remote compact".into(), text_elements: Vec::new(), }], @@ -1273,7 +1257,6 @@ async fn remote_compact_runs_automatically() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello remote compact".into(), text_elements: Vec::new(), }], @@ -1412,7 +1395,6 @@ async fn remote_compact_trims_function_call_history_to_fit_context_window() -> R .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: first_user_message.into(), text_elements: Vec::new(), }], @@ -1428,7 +1410,6 @@ async fn remote_compact_trims_function_call_history_to_fit_context_window() -> R .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: second_user_message.into(), text_elements: Vec::new(), }], @@ -1548,7 +1529,6 @@ async fn auto_remote_compact_trims_function_call_history_to_fit_context_window() .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: first_user_message.into(), text_elements: Vec::new(), }], @@ -1564,7 +1544,6 @@ async fn auto_remote_compact_trims_function_call_history_to_fit_context_window() .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: second_user_message.into(), text_elements: Vec::new(), }], @@ -1586,7 +1565,6 @@ async fn auto_remote_compact_trims_function_call_history_to_fit_context_window() .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "turn that triggers auto compact".into(), text_elements: Vec::new(), }], @@ -1688,7 +1666,6 @@ async fn auto_remote_compact_failure_stops_agent_loop() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "turn that exceeds token threshold".into(), text_elements: Vec::new(), }], @@ -1704,7 +1681,6 @@ async fn auto_remote_compact_failure_stops_agent_loop() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "turn that triggers auto compact".into(), text_elements: Vec::new(), }], @@ -1800,7 +1776,6 @@ async fn remote_compact_trim_estimate_uses_session_base_instructions() -> Result .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: first_user_message.into(), text_elements: Vec::new(), }], @@ -1819,7 +1794,6 @@ async fn remote_compact_trim_estimate_uses_session_base_instructions() -> Result .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: second_user_message.into(), text_elements: Vec::new(), }], @@ -1912,7 +1886,6 @@ async fn remote_compact_trim_estimate_uses_session_base_instructions() -> Result .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: first_user_message.into(), text_elements: Vec::new(), }], @@ -1931,7 +1904,6 @@ async fn remote_compact_trim_estimate_uses_session_base_instructions() -> Result .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: second_user_message.into(), text_elements: Vec::new(), }], @@ -2004,7 +1976,6 @@ async fn remote_manual_compact_emits_context_compaction_items() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "manual remote compact".into(), text_elements: Vec::new(), }], @@ -2087,7 +2058,6 @@ async fn remote_manual_compact_failure_emits_task_error_event() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "manual remote compact".into(), text_elements: Vec::new(), }], @@ -2173,7 +2143,6 @@ async fn remote_compact_persists_replacement_history_in_rollout() -> Result<()> .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "needs compaction".into(), text_elements: Vec::new(), }], @@ -2318,7 +2287,6 @@ async fn remote_compact_and_resume_refresh_stale_developer_instructions() -> Res .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "start remote compact flow".into(), text_elements: Vec::new(), }], @@ -2338,7 +2306,6 @@ async fn remote_compact_and_resume_refresh_stale_developer_instructions() -> Res .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "after compact in same session".into(), text_elements: Vec::new(), }], @@ -2365,7 +2332,6 @@ async fn remote_compact_and_resume_refresh_stale_developer_instructions() -> Res .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "after resume".into(), text_elements: Vec::new(), }], @@ -2463,7 +2429,6 @@ async fn remote_compact_refreshes_stale_developer_instructions_without_resume() .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "start remote compact flow".into(), text_elements: Vec::new(), }], @@ -2482,7 +2447,6 @@ async fn remote_compact_refreshes_stale_developer_instructions_without_resume() .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "after compact in same session".into(), text_elements: Vec::new(), }], @@ -2556,7 +2520,6 @@ async fn snapshot_request_shape_remote_pre_turn_compaction_restates_realtime_sta .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "USER_ONE".to_string(), text_elements: Vec::new(), }], @@ -2572,7 +2535,6 @@ async fn snapshot_request_shape_remote_pre_turn_compaction_restates_realtime_sta .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "USER_TWO".to_string(), text_elements: Vec::new(), }], @@ -2641,7 +2603,6 @@ async fn remote_request_uses_custom_experimental_realtime_start_instructions() - .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "USER_ONE".to_string(), text_elements: Vec::new(), }], @@ -2704,7 +2665,6 @@ async fn snapshot_request_shape_remote_pre_turn_compaction_restates_realtime_end .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "USER_ONE".to_string(), text_elements: Vec::new(), }], @@ -2722,7 +2682,6 @@ async fn snapshot_request_shape_remote_pre_turn_compaction_restates_realtime_end .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "USER_TWO".to_string(), text_elements: Vec::new(), }], @@ -2799,7 +2758,6 @@ async fn snapshot_request_shape_remote_manual_compact_restates_realtime_start() .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "USER_ONE".to_string(), text_elements: Vec::new(), }], @@ -2818,7 +2776,6 @@ async fn snapshot_request_shape_remote_manual_compact_restates_realtime_start() .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "USER_TWO".to_string(), text_elements: Vec::new(), }], @@ -2903,7 +2860,6 @@ async fn snapshot_request_shape_remote_mid_turn_compaction_does_not_restate_real .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "SETUP_USER".to_string(), text_elements: Vec::new(), }], @@ -2921,7 +2877,6 @@ async fn snapshot_request_shape_remote_mid_turn_compaction_does_not_restate_real .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "USER_TWO".to_string(), text_elements: Vec::new(), }], @@ -3014,7 +2969,6 @@ async fn snapshot_request_shape_remote_compact_resume_restates_realtime_end() -> .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "USER_ONE".to_string(), text_elements: Vec::new(), }], @@ -3046,7 +3000,6 @@ async fn snapshot_request_shape_remote_compact_resume_restates_realtime_end() -> .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "USER_TWO".to_string(), text_elements: Vec::new(), }], @@ -3137,7 +3090,6 @@ async fn snapshot_request_shape_remote_pre_turn_compaction_including_incoming_us .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: user.to_string(), text_elements: Vec::new(), }], @@ -3226,7 +3178,6 @@ async fn snapshot_request_shape_remote_pre_turn_compaction_strips_incoming_model .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "BEFORE_SWITCH_USER".to_string(), text_elements: Vec::new(), }], @@ -3250,7 +3201,6 @@ async fn snapshot_request_shape_remote_pre_turn_compaction_strips_incoming_model .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "AFTER_SWITCH_USER".to_string(), text_elements: Vec::new(), }], @@ -3371,7 +3321,6 @@ async fn snapshot_request_shape_remote_pre_turn_compaction_context_window_exceed .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "USER_ONE".to_string(), text_elements: Vec::new(), }], @@ -3387,7 +3336,6 @@ async fn snapshot_request_shape_remote_pre_turn_compaction_context_window_exceed .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "USER_TWO".to_string(), text_elements: Vec::new(), }], @@ -3474,7 +3422,6 @@ async fn snapshot_request_shape_remote_mid_turn_continuation_compaction() -> Res .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "USER_ONE".to_string(), text_elements: Vec::new(), }], @@ -3554,7 +3501,6 @@ async fn snapshot_request_shape_remote_mid_turn_compaction_summary_only_reinject .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "USER_ONE".to_string(), text_elements: Vec::new(), }], @@ -3642,7 +3588,6 @@ async fn snapshot_request_shape_remote_mid_turn_compaction_multi_summary_reinjec .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "USER_ONE".to_string(), text_elements: Vec::new(), }], @@ -3661,7 +3606,6 @@ async fn snapshot_request_shape_remote_mid_turn_compaction_multi_summary_reinjec .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "USER_TWO".to_string(), text_elements: Vec::new(), }], @@ -3745,7 +3689,6 @@ async fn snapshot_request_shape_remote_manual_compact_without_previous_user_mess .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "USER_ONE".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/compact_remote_parity.rs b/codex-rs/core/tests/suite/compact_remote_parity.rs index 8ad0ff55806..22678fc46f7 100644 --- a/codex-rs/core/tests/suite/compact_remote_parity.rs +++ b/codex-rs/core/tests/suite/compact_remote_parity.rs @@ -319,7 +319,6 @@ async fn run_manual_session( submit_user_input( &codex, vec![UserInput::Text { - client_id: None, text: format!("{}_AFTER_COMPACT_USER", scenario.name), text_elements: Vec::new(), }], @@ -370,7 +369,6 @@ async fn run_pre_turn_auto_session(mode: Mode) -> Result { submit_user_input( &codex, vec![UserInput::Text { - client_id: None, text: "pre_turn_auto_before".to_string(), text_elements: Vec::new(), }], @@ -379,7 +377,6 @@ async fn run_pre_turn_auto_session(mode: Mode) -> Result { submit_user_input( &codex, vec![UserInput::Text { - client_id: None, text: "pre_turn_auto_after".to_string(), text_elements: Vec::new(), }], @@ -430,7 +427,6 @@ async fn run_mid_turn_auto_session(mode: Mode) -> Result { submit_user_input( &codex, vec![UserInput::Text { - client_id: None, text: "mid_turn_auto_user".to_string(), text_elements: Vec::new(), }], @@ -470,7 +466,6 @@ async fn run_manual_hook_session(mode: Mode) -> Result { submit_user_input( &codex, vec![UserInput::Text { - client_id: None, text: "manual_hooks_before".to_string(), text_elements: Vec::new(), }], @@ -627,13 +622,11 @@ fn user_input_for_step(scenario_name: &str, idx: usize, step: Step) -> Vec, text: &str) { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: text.into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/exec_policy.rs b/codex-rs/core/tests/suite/exec_policy.rs index 1c19bfd5c61..9e350d27730 100644 --- a/codex-rs/core/tests/suite/exec_policy.rs +++ b/codex-rs/core/tests/suite/exec_policy.rs @@ -48,7 +48,6 @@ async fn submit_user_turn( test.codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: prompt.into(), text_elements: Vec::new(), }], @@ -137,7 +136,6 @@ async fn execpolicy_blocks_shell_invocation() -> Result<()> { test.codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: "run shell command".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/fork_thread.rs b/codex-rs/core/tests/suite/fork_thread.rs index b51e7b28072..544c530aa08 100644 --- a/codex-rs/core/tests/suite/fork_thread.rs +++ b/codex-rs/core/tests/suite/fork_thread.rs @@ -52,7 +52,6 @@ async fn fork_thread_twice_drops_to_first_message() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: text.to_string(), text_elements: Vec::new(), }], @@ -178,7 +177,6 @@ async fn fork_thread_from_history_does_not_require_source_rollout_path() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "fork me from stored history".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/hooks.rs b/codex-rs/core/tests/suite/hooks.rs index aa6b167fe0c..a535eb4b8e2 100644 --- a/codex-rs/core/tests/suite/hooks.rs +++ b/codex-rs/core/tests/suite/hooks.rs @@ -1845,7 +1845,6 @@ async fn blocked_queued_prompt_does_not_strand_earlier_accepted_prompt() -> Resu .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "initial prompt".to_string(), text_elements: Vec::new(), }], @@ -1866,7 +1865,6 @@ async fn blocked_queued_prompt_does_not_strand_earlier_accepted_prompt() -> Resu .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: text.to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/image_rollout.rs b/codex-rs/core/tests/suite/image_rollout.rs index 8ead712911a..87eb03079e8 100644 --- a/codex-rs/core/tests/suite/image_rollout.rs +++ b/codex-rs/core/tests/suite/image_rollout.rs @@ -116,12 +116,10 @@ async fn copy_paste_local_image_persists_rollout_request_shape() -> anyhow::Resu .submit(Op::UserInput { items: vec![ UserInput::LocalImage { - client_id: None, path: abs_path.clone(), detail: None, }, UserInput::Text { - client_id: None, text: "pasted image".to_string(), text_elements: Vec::new(), }, @@ -215,12 +213,10 @@ async fn drag_drop_image_persists_rollout_request_shape() -> anyhow::Result<()> .submit(Op::UserInput { items: vec![ UserInput::Image { - client_id: None, image_url: image_url.clone(), detail: None, }, UserInput::Text { - client_id: None, text: "dropped image".to_string(), text_elements: Vec::new(), }, diff --git a/codex-rs/core/tests/suite/items.rs b/codex-rs/core/tests/suite/items.rs index beabe0c9381..a56b2ab95f5 100644 --- a/codex-rs/core/tests/suite/items.rs +++ b/codex-rs/core/tests/suite/items.rs @@ -52,7 +52,6 @@ fn disabled_plan_turn( turn_permission_fields(PermissionProfile::Disabled, cwd.as_path()); Ok(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: text.into(), text_elements: Vec::new(), }], @@ -111,7 +110,6 @@ async fn user_message_item_is_emitted() -> anyhow::Result<()> { Some("".into()), )]; let expected_input = UserInput::Text { - client_id: None, text: "please inspect sample.txt".into(), text_elements: text_elements.clone(), }; @@ -177,7 +175,6 @@ async fn assistant_message_item_is_emitted() -> anyhow::Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "please summarize results".into(), text_elements: Vec::new(), }], @@ -240,7 +237,6 @@ async fn reasoning_item_is_emitted() -> anyhow::Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "explain your reasoning".into(), text_elements: Vec::new(), }], @@ -304,7 +300,6 @@ async fn web_search_item_is_emitted() -> anyhow::Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "find the weather".into(), text_elements: Vec::new(), }], @@ -386,7 +381,6 @@ async fn image_generation_call_event_is_emitted() -> anyhow::Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "generate a tiny blue square".into(), text_elements: Vec::new(), }], @@ -475,7 +469,6 @@ async fn image_generation_call_event_is_emitted_when_image_save_fails() -> anyho .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "generate an image".into(), text_elements: Vec::new(), }], @@ -533,7 +526,6 @@ async fn agent_message_content_delta_has_item_metadata() -> anyhow::Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "please stream text".into(), text_elements: Vec::new(), }], @@ -1119,7 +1111,6 @@ async fn reasoning_content_delta_has_item_metadata() -> anyhow::Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "reason through it".into(), text_elements: Vec::new(), }], @@ -1176,7 +1167,6 @@ async fn reasoning_raw_content_delta_respects_flag() -> anyhow::Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "show raw reasoning".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/json_result.rs b/codex-rs/core/tests/suite/json_result.rs index eb7e4c42cdf..67275d31468 100644 --- a/codex-rs/core/tests/suite/json_result.rs +++ b/codex-rs/core/tests/suite/json_result.rs @@ -77,7 +77,6 @@ async fn codex_returns_json_result(model: String) -> anyhow::Result<()> { codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: "hello world".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/mcp_turn_metadata.rs b/codex-rs/core/tests/suite/mcp_turn_metadata.rs index 475751ce394..897d8621628 100644 --- a/codex-rs/core/tests/suite/mcp_turn_metadata.rs +++ b/codex-rs/core/tests/suite/mcp_turn_metadata.rs @@ -70,7 +70,6 @@ async fn submit_user_turn( test.codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: text.to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/model_switching.rs b/codex-rs/core/tests/suite/model_switching.rs index c9f5c17fc00..3c4dd40b32f 100644 --- a/codex-rs/core/tests/suite/model_switching.rs +++ b/codex-rs/core/tests/suite/model_switching.rs @@ -156,7 +156,6 @@ async fn model_change_appends_model_instructions_developer_message() -> Result<( .submit(read_only_user_turn( &test, vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -178,7 +177,6 @@ async fn model_change_appends_model_instructions_developer_message() -> Result<( .submit(read_only_user_turn( &test, vec![UserInput::Text { - client_id: None, text: "switch models".into(), text_elements: Vec::new(), }], @@ -230,7 +228,6 @@ async fn model_and_personality_change_only_appends_model_instructions() -> Resul .submit(read_only_user_turn( &test, vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -253,7 +250,6 @@ async fn model_and_personality_change_only_appends_model_instructions() -> Resul .submit(read_only_user_turn( &test, vec![UserInput::Text { - client_id: None, text: "switch model and personality".into(), text_elements: Vec::new(), }], @@ -513,12 +509,10 @@ async fn model_change_from_image_to_text_strips_prior_image_content() -> Result< &test, vec![ UserInput::Image { - client_id: None, image_url: image_url.clone(), detail: None, }, UserInput::Text { - client_id: None, text: "first turn".to_string(), text_elements: Vec::new(), }, @@ -532,7 +526,6 @@ async fn model_change_from_image_to_text_strips_prior_image_content() -> Result< .submit(read_only_user_turn( &test, vec![UserInput::Text { - client_id: None, text: "second turn".to_string(), text_elements: Vec::new(), }], @@ -619,7 +612,6 @@ async fn generated_image_is_replayed_for_image_capable_models() -> Result<()> { .submit(read_only_user_turn( &test, vec![UserInput::Text { - client_id: None, text: "generate a lobster".to_string(), text_elements: Vec::new(), }], @@ -632,7 +624,6 @@ async fn generated_image_is_replayed_for_image_capable_models() -> Result<()> { .submit(read_only_user_turn( &test, vec![UserInput::Text { - client_id: None, text: "describe the generated image".to_string(), text_elements: Vec::new(), }], @@ -735,7 +726,6 @@ async fn model_change_from_generated_image_to_text_preserves_prior_generated_ima .submit(read_only_user_turn( &test, vec![UserInput::Text { - client_id: None, text: "generate a lobster".to_string(), text_elements: Vec::new(), }], @@ -748,7 +738,6 @@ async fn model_change_from_generated_image_to_text_preserves_prior_generated_ima .submit(read_only_user_turn( &test, vec![UserInput::Text { - client_id: None, text: "describe the generated image".to_string(), text_elements: Vec::new(), }], @@ -853,7 +842,6 @@ async fn thread_rollback_after_generated_image_drops_entire_image_turn_history() .submit(read_only_user_turn( &test, vec![UserInput::Text { - client_id: None, text: "generate a lobster".to_string(), text_elements: Vec::new(), }], @@ -874,7 +862,6 @@ async fn thread_rollback_after_generated_image_drops_entire_image_turn_history() .submit(read_only_user_turn( &test, vec![UserInput::Text { - client_id: None, text: "after rollback".to_string(), text_elements: Vec::new(), }], @@ -1025,7 +1012,6 @@ async fn model_switch_to_smaller_model_updates_token_context_window() -> Result< .submit(read_only_user_turn( &test, vec![UserInput::Text { - client_id: None, text: "use larger model".into(), text_elements: Vec::new(), }], @@ -1069,7 +1055,6 @@ async fn model_switch_to_smaller_model_updates_token_context_window() -> Result< .submit(read_only_user_turn( &test, vec![UserInput::Text { - client_id: None, text: "switch to smaller model".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/model_visible_layout.rs b/codex-rs/core/tests/suite/model_visible_layout.rs index 37ce5018510..e02e1ffaefe 100644 --- a/codex-rs/core/tests/suite/model_visible_layout.rs +++ b/codex-rs/core/tests/suite/model_visible_layout.rs @@ -119,7 +119,6 @@ async fn snapshot_model_visible_layout_turn_overrides() -> Result<()> { test.codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: "first turn".into(), text_elements: Vec::new(), }], @@ -156,7 +155,6 @@ async fn snapshot_model_visible_layout_turn_overrides() -> Result<()> { test.codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: "second turn with context updates".into(), text_elements: Vec::new(), }], @@ -247,7 +245,6 @@ async fn snapshot_model_visible_layout_cwd_change_does_not_refresh_agents() -> R test.codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: "first turn in agents_one".into(), text_elements: Vec::new(), }], @@ -282,7 +279,6 @@ async fn snapshot_model_visible_layout_cwd_change_does_not_refresh_agents() -> R test.codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: "second turn in agents_two".into(), text_elements: Vec::new(), }], @@ -368,7 +364,6 @@ async fn snapshot_model_visible_layout_resume_with_personality_change() -> Resul .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "seed resume history".into(), text_elements: Vec::new(), }], @@ -410,7 +405,6 @@ async fn snapshot_model_visible_layout_resume_with_personality_change() -> Resul .codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: "resume and change personality".into(), text_elements: Vec::new(), }], @@ -486,7 +480,6 @@ async fn snapshot_model_visible_layout_resume_override_matches_rollout_model() - .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "seed resume history".into(), text_elements: Vec::new(), }], @@ -529,7 +522,6 @@ async fn snapshot_model_visible_layout_resume_override_matches_rollout_model() - .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "first resumed turn after model override".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/models_cache_ttl.rs b/codex-rs/core/tests/suite/models_cache_ttl.rs index e127b890184..fcd90cbaeab 100644 --- a/codex-rs/core/tests/suite/models_cache_ttl.rs +++ b/codex-rs/core/tests/suite/models_cache_ttl.rs @@ -94,7 +94,6 @@ async fn renews_cache_ttl_on_matching_models_etag() -> Result<()> { codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: "hi".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/models_etag_responses.rs b/codex-rs/core/tests/suite/models_etag_responses.rs index c776a609036..cbd73fffe9c 100644 --- a/codex-rs/core/tests/suite/models_etag_responses.rs +++ b/codex-rs/core/tests/suite/models_etag_responses.rs @@ -106,7 +106,6 @@ async fn refresh_models_on_models_etag_mismatch_and_avoid_duplicate_models_fetch codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: "please run a tool".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/otel.rs b/codex-rs/core/tests/suite/otel.rs index 251112f5f3e..7f5fa260cd6 100644 --- a/codex-rs/core/tests/suite/otel.rs +++ b/codex-rs/core/tests/suite/otel.rs @@ -117,7 +117,6 @@ async fn responses_api_emits_api_request_event() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -165,7 +164,6 @@ async fn process_sse_emits_tracing_for_output_item() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -213,7 +211,6 @@ async fn process_sse_emits_failed_event_on_parse_error() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -262,7 +259,6 @@ async fn process_sse_records_failed_event_when_stream_closes_without_completed() .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -331,7 +327,6 @@ async fn process_sse_failed_event_records_response_error_message() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -398,7 +393,6 @@ async fn process_sse_failed_event_logs_parse_error() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -452,7 +446,6 @@ async fn process_sse_failed_event_logs_missing_error() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -515,7 +508,6 @@ async fn process_sse_failed_event_logs_response_completed_parse_error() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -572,7 +564,6 @@ async fn process_sse_emits_completed_telemetry() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -653,7 +644,6 @@ async fn turn_and_completed_response_spans_record_token_usage() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -743,7 +733,6 @@ async fn handle_responses_span_records_response_kind_and_tool_name() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -839,7 +828,6 @@ async fn record_responses_sets_span_fields_for_response_events() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -930,7 +918,6 @@ async fn handle_response_item_records_tool_result_for_custom_tool_call() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -1008,7 +995,6 @@ async fn handle_response_item_records_tool_result_for_function_call() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -1087,7 +1073,6 @@ async fn handle_response_item_records_tool_result_for_shell_command_call() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -1198,7 +1183,6 @@ async fn handle_shell_command_autoapprove_from_config_records_tool_decision() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -1255,7 +1239,6 @@ async fn handle_shell_command_user_approved_records_tool_decision() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "approved".into(), text_elements: Vec::new(), }], @@ -1327,7 +1310,6 @@ async fn handle_shell_command_user_approved_for_session_records_tool_decision() .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "persist".into(), text_elements: Vec::new(), }], @@ -1399,7 +1381,6 @@ async fn handle_sandbox_error_user_approves_retry_records_tool_decision() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "retry".into(), text_elements: Vec::new(), }], @@ -1471,7 +1452,6 @@ async fn handle_shell_command_user_denies_records_tool_decision() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "deny".into(), text_elements: Vec::new(), }], @@ -1543,7 +1523,6 @@ async fn handle_sandbox_error_user_approves_for_session_records_tool_decision() .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "persist".into(), text_elements: Vec::new(), }], @@ -1616,7 +1595,6 @@ async fn handle_sandbox_error_user_denies_records_tool_decision() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "deny".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/pending_input.rs b/codex-rs/core/tests/suite/pending_input.rs index e92185bf91b..96b89caec84 100644 --- a/codex-rs/core/tests/suite/pending_input.rs +++ b/codex-rs/core/tests/suite/pending_input.rs @@ -98,7 +98,6 @@ async fn submit_user_input(codex: &CodexThread, text: &str) { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: text.to_string(), text_elements: Vec::new(), }], @@ -117,7 +116,6 @@ async fn submit_danger_full_access_user_turn(test: &TestCodex, text: &str) { test.codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: text.to_string(), text_elements: Vec::new(), }], @@ -149,12 +147,12 @@ async fn steer_user_input(codex: &CodexThread, text: &str) { codex .steer_input( vec![UserInput::Text { - client_id: None, text: text.to_string(), text_elements: Vec::new(), }], /*additional_context*/ Default::default(), /*expected_turn_id*/ None, + /*client_user_message_id*/ None, /*responsesapi_client_metadata*/ None, ) .await @@ -292,7 +290,6 @@ async fn injected_user_input_triggers_follow_up_request_with_deltas() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "first prompt".into(), text_elements: Vec::new(), }], @@ -313,7 +310,6 @@ async fn injected_user_input_triggers_follow_up_request_with_deltas() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "second prompt".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/permissions_messages.rs b/codex-rs/core/tests/suite/permissions_messages.rs index c1c08226798..45d2e32549f 100644 --- a/codex-rs/core/tests/suite/permissions_messages.rs +++ b/codex-rs/core/tests/suite/permissions_messages.rs @@ -53,7 +53,6 @@ async fn permissions_message_sent_once_on_start() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -95,7 +94,6 @@ async fn permissions_message_added_on_override_change() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello 1".into(), text_elements: Vec::new(), }], @@ -120,7 +118,6 @@ async fn permissions_message_added_on_override_change() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello 2".into(), text_elements: Vec::new(), }], @@ -168,7 +165,6 @@ async fn permissions_message_not_added_when_no_change() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello 1".into(), text_elements: Vec::new(), }], @@ -184,7 +180,6 @@ async fn permissions_message_not_added_when_no_change() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello 2".into(), text_elements: Vec::new(), }], @@ -232,7 +227,6 @@ async fn permissions_message_omitted_when_disabled() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello 1".into(), text_elements: Vec::new(), }], @@ -257,7 +251,6 @@ async fn permissions_message_omitted_when_disabled() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello 2".into(), text_elements: Vec::new(), }], @@ -318,7 +311,6 @@ async fn resume_replays_permissions_messages() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello 1".into(), text_elements: Vec::new(), }], @@ -344,7 +336,6 @@ async fn resume_replays_permissions_messages() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello 2".into(), text_elements: Vec::new(), }], @@ -362,7 +353,6 @@ async fn resume_replays_permissions_messages() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "after resume".into(), text_elements: Vec::new(), }], @@ -424,7 +414,6 @@ async fn resume_and_fork_append_permissions_messages() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello 1".into(), text_elements: Vec::new(), }], @@ -450,7 +439,6 @@ async fn resume_and_fork_append_permissions_messages() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello 2".into(), text_elements: Vec::new(), }], @@ -474,7 +462,6 @@ async fn resume_and_fork_append_permissions_messages() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "after resume".into(), text_elements: Vec::new(), }], @@ -512,7 +499,6 @@ async fn resume_and_fork_append_permissions_messages() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "after fork".into(), text_elements: Vec::new(), }], @@ -575,7 +561,6 @@ async fn permissions_message_includes_writable_roots() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/personality.rs b/codex-rs/core/tests/suite/personality.rs index 220f0698374..06781dc11d4 100644 --- a/codex-rs/core/tests/suite/personality.rs +++ b/codex-rs/core/tests/suite/personality.rs @@ -62,7 +62,6 @@ fn read_only_text_turn_with_personality( turn_permission_fields(PermissionProfile::read_only(), test.cwd_path()); Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: text.into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/plugins.rs b/codex-rs/core/tests/suite/plugins.rs index fa7efb062b7..b89b611a83c 100644 --- a/codex-rs/core/tests/suite/plugins.rs +++ b/codex-rs/core/tests/suite/plugins.rs @@ -222,7 +222,6 @@ async fn capability_sections_render_in_developer_message_in_order() -> Result<() .submit(Op::UserInput { environments: None, items: vec![codex_protocol::user_input::UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], @@ -303,7 +302,6 @@ async fn explicit_plugin_mentions_inject_plugin_guidance() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![codex_protocol::user_input::UserInput::Mention { - client_id: None, name: "sample".into(), path: format!("plugin://{SAMPLE_PLUGIN_CONFIG_NAME}"), }], @@ -387,7 +385,6 @@ async fn explicit_plugin_mentions_track_plugin_used_analytics() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![codex_protocol::user_input::UserInput::Mention { - client_id: None, name: "sample".into(), path: format!("plugin://{SAMPLE_PLUGIN_CONFIG_NAME}"), }], diff --git a/codex-rs/core/tests/suite/prompt_caching.rs b/codex-rs/core/tests/suite/prompt_caching.rs index 0889d16dafb..836761961a3 100644 --- a/codex-rs/core/tests/suite/prompt_caching.rs +++ b/codex-rs/core/tests/suite/prompt_caching.rs @@ -148,7 +148,6 @@ async fn prompt_tools_are_consistent_across_requests() -> anyhow::Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello 1".into(), text_elements: Vec::new(), }], @@ -164,7 +163,6 @@ async fn prompt_tools_are_consistent_across_requests() -> anyhow::Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello 2".into(), text_elements: Vec::new(), }], @@ -249,7 +247,6 @@ async fn gpt_5_tools_without_apply_patch_append_apply_patch_instructions() -> an .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello 1".into(), text_elements: Vec::new(), }], @@ -265,7 +262,6 @@ async fn gpt_5_tools_without_apply_patch_append_apply_patch_instructions() -> an .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello 2".into(), text_elements: Vec::new(), }], @@ -332,7 +328,6 @@ async fn prefixes_context_and_instructions_once_and_consistently_across_requests .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello 1".into(), text_elements: Vec::new(), }], @@ -348,7 +343,6 @@ async fn prefixes_context_and_instructions_once_and_consistently_across_requests .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello 2".into(), text_elements: Vec::new(), }], @@ -433,7 +427,6 @@ async fn overrides_turn_context_but_keeps_cached_prefix_and_key_constant() -> an .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello 1".into(), text_elements: Vec::new(), }], @@ -473,7 +466,6 @@ async fn overrides_turn_context_but_keeps_cached_prefix_and_key_constant() -> an .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello 2".into(), text_elements: Vec::new(), }], @@ -556,7 +548,6 @@ async fn override_before_first_turn_emits_environment_context() -> anyhow::Resul .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "first message".into(), text_elements: Vec::new(), }], @@ -712,7 +703,6 @@ async fn per_turn_overrides_keep_cached_prefix_and_key_constant() -> anyhow::Res .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello 1".into(), text_elements: Vec::new(), }], @@ -738,7 +728,6 @@ async fn per_turn_overrides_keep_cached_prefix_and_key_constant() -> anyhow::Res codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: "hello 2".into(), text_elements: Vec::new(), }], @@ -854,7 +843,6 @@ async fn send_user_turn_with_no_changes_does_not_send_environment_context() -> a codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: "hello 1".into(), text_elements: Vec::new(), }], @@ -884,7 +872,6 @@ async fn send_user_turn_with_no_changes_does_not_send_environment_context() -> a codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: "hello 2".into(), text_elements: Vec::new(), }], @@ -997,7 +984,6 @@ async fn send_user_turn_with_changes_sends_environment_context() -> anyhow::Resu codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: "hello 1".into(), text_elements: Vec::new(), }], @@ -1029,7 +1015,6 @@ async fn send_user_turn_with_changes_sends_environment_context() -> anyhow::Resu codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: "hello 2".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/prompt_debug_tests.rs b/codex-rs/core/tests/suite/prompt_debug_tests.rs index d46d6f4f0ae..dc506bc4746 100644 --- a/codex-rs/core/tests/suite/prompt_debug_tests.rs +++ b/codex-rs/core/tests/suite/prompt_debug_tests.rs @@ -26,7 +26,6 @@ async fn build_prompt_input_includes_context_and_user_message() -> Result<()> { let input = build_prompt_input( config, vec![UserInput::Text { - client_id: None, text: "hello from debug prompt".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/quota_exceeded.rs b/codex-rs/core/tests/suite/quota_exceeded.rs index 0da8d78b0e6..904c116cfbc 100644 --- a/codex-rs/core/tests/suite/quota_exceeded.rs +++ b/codex-rs/core/tests/suite/quota_exceeded.rs @@ -43,7 +43,6 @@ async fn quota_exceeded_emits_single_error_event() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "quota?".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/realtime_conversation.rs b/codex-rs/core/tests/suite/realtime_conversation.rs index 25485e8d13c..b99333e66c8 100644 --- a/codex-rs/core/tests/suite/realtime_conversation.rs +++ b/codex-rs/core/tests/suite/realtime_conversation.rs @@ -2161,7 +2161,6 @@ async fn conversation_user_text_turn_is_sent_to_realtime_when_active() -> Result .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: user_text.to_string(), text_elements: Vec::new(), }], @@ -2298,7 +2297,6 @@ async fn conversation_user_text_turn_is_capped_when_mirrored_to_realtime() -> Re .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: user_text.clone(), text_elements: Vec::new(), }], @@ -3496,7 +3494,6 @@ async fn inbound_handoff_request_steers_active_turn() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "first prompt".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/remote_env.rs b/codex-rs/core/tests/suite/remote_env.rs index 58981538de1..34cd119f66a 100644 --- a/codex-rs/core/tests/suite/remote_env.rs +++ b/codex-rs/core/tests/suite/remote_env.rs @@ -69,7 +69,6 @@ async fn submit_turn_with_approval_and_environments( test.codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: prompt.into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/remote_models.rs b/codex-rs/core/tests/suite/remote_models.rs index c703aad33d4..b7b16f9c292 100644 --- a/codex-rs/core/tests/suite/remote_models.rs +++ b/codex-rs/core/tests/suite/remote_models.rs @@ -154,7 +154,6 @@ async fn remote_models_config_context_window_override_clamps_to_max_context_wind codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: "check context window".into(), text_elements: Vec::new(), }], @@ -223,7 +222,6 @@ async fn remote_models_config_override_above_max_uses_max_context_window() -> Re codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: "check context window".into(), text_elements: Vec::new(), }], @@ -291,7 +289,6 @@ async fn remote_models_use_context_window_when_config_override_is_absent() -> Re codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: "check context window".into(), text_elements: Vec::new(), }], @@ -372,7 +369,6 @@ async fn remote_models_long_model_slug_is_sent_with_high_reasoning() -> Result<( codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: "check model slug".into(), text_elements: Vec::new(), }], @@ -424,7 +420,6 @@ async fn namespaced_model_slug_uses_catalog_metadata_without_fallback_warning() codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: "check namespaced model metadata".into(), text_elements: Vec::new(), }], @@ -579,7 +574,6 @@ async fn remote_models_remote_model_uses_unified_exec() -> Result<()> { codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: "run call".into(), text_elements: Vec::new(), }], @@ -804,7 +798,6 @@ async fn remote_models_apply_remote_base_instructions() -> Result<()> { codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: "hello remote".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/request_compression.rs b/codex-rs/core/tests/suite/request_compression.rs index 423b73a6d5f..fe9cb1e5f84 100644 --- a/codex-rs/core/tests/suite/request_compression.rs +++ b/codex-rs/core/tests/suite/request_compression.rs @@ -42,7 +42,6 @@ async fn request_body_is_zstd_compressed_for_codex_backend_when_enabled() -> any .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "compress me".into(), text_elements: Vec::new(), }], @@ -94,7 +93,6 @@ async fn request_body_is_not_compressed_for_api_key_auth_even_when_enabled() -> .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "do not compress".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/request_permissions.rs b/codex-rs/core/tests/suite/request_permissions.rs index 5ca975ea5f1..a78e4a016eb 100644 --- a/codex-rs/core/tests/suite/request_permissions.rs +++ b/codex-rs/core/tests/suite/request_permissions.rs @@ -192,7 +192,6 @@ async fn submit_turn( test.codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: prompt.into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/request_permissions_tool.rs b/codex-rs/core/tests/suite/request_permissions_tool.rs index dde8d15f978..a122cc399b3 100644 --- a/codex-rs/core/tests/suite/request_permissions_tool.rs +++ b/codex-rs/core/tests/suite/request_permissions_tool.rs @@ -144,7 +144,6 @@ async fn submit_turn( test.codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: prompt.into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/request_user_input.rs b/codex-rs/core/tests/suite/request_user_input.rs index 3c8c8183235..d09071d72d9 100644 --- a/codex-rs/core/tests/suite/request_user_input.rs +++ b/codex-rs/core/tests/suite/request_user_input.rs @@ -138,7 +138,6 @@ async fn request_user_input_round_trip_for_mode(mode: ModeKind) -> anyhow::Resul codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: "please confirm".into(), text_elements: Vec::new(), }], @@ -283,7 +282,6 @@ async fn request_user_input_interrupt_emits_deferred_token_count() -> anyhow::Re codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: "please confirm".into(), text_elements: Vec::new(), }], @@ -389,7 +387,6 @@ where codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: "please confirm".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/responses_api_proxy_headers.rs b/codex-rs/core/tests/suite/responses_api_proxy_headers.rs index ccd45d008d8..0c401d05fc8 100644 --- a/codex-rs/core/tests/suite/responses_api_proxy_headers.rs +++ b/codex-rs/core/tests/suite/responses_api_proxy_headers.rs @@ -146,7 +146,6 @@ async fn submit_turn_with_timeout(test: &TestCodex, prompt: &str) -> Result<()> test.codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: prompt.into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/resume.rs b/codex-rs/core/tests/suite/resume.rs index a6e610a7770..0dc7eaddcbd 100644 --- a/codex-rs/core/tests/suite/resume.rs +++ b/codex-rs/core/tests/suite/resume.rs @@ -88,7 +88,6 @@ async fn resume_includes_initial_messages_from_rollout_events() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "Record some messages".into(), text_elements: text_elements.clone(), }], @@ -176,7 +175,6 @@ async fn resume_includes_initial_messages_from_reasoning_events() -> Result<()> .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "Record reasoning messages".into(), text_elements: Vec::new(), }], @@ -268,7 +266,6 @@ async fn resume_switches_models_preserves_base_instructions() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "Record initial instructions".into(), text_elements: Vec::new(), }], @@ -313,7 +310,6 @@ async fn resume_switches_models_preserves_base_instructions() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "Resume with different model".into(), text_elements: Vec::new(), }], @@ -333,7 +329,6 @@ async fn resume_switches_models_preserves_base_instructions() -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "Second turn after resume".into(), text_elements: Vec::new(), }], @@ -408,7 +403,6 @@ async fn resume_model_switch_is_not_duplicated_after_pre_turn_override() -> Resu .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "Record initial instructions".into(), text_elements: Vec::new(), }], @@ -448,7 +442,6 @@ async fn resume_model_switch_is_not_duplicated_after_pre_turn_override() -> Resu .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "first turn after override".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/review.rs b/codex-rs/core/tests/suite/review.rs index 698c0b7579c..9efaf6c2d12 100644 --- a/codex-rs/core/tests/suite/review.rs +++ b/codex-rs/core/tests/suite/review.rs @@ -706,7 +706,6 @@ async fn review_history_surfaces_in_parent_session() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: followup.clone(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/rmcp_client.rs b/codex-rs/core/tests/suite/rmcp_client.rs index 5811d985abd..2c86cb393ce 100644 --- a/codex-rs/core/tests/suite/rmcp_client.rs +++ b/codex-rs/core/tests/suite/rmcp_client.rs @@ -125,7 +125,6 @@ fn user_turn_with_permission_profile( turn_permission_fields(permission_profile, cwd.as_path()); Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: text.into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/safety_check_downgrade.rs b/codex-rs/core/tests/suite/safety_check_downgrade.rs index 5f9b9a83303..3d4cb93b9c5 100644 --- a/codex-rs/core/tests/suite/safety_check_downgrade.rs +++ b/codex-rs/core/tests/suite/safety_check_downgrade.rs @@ -39,7 +39,6 @@ fn disabled_text_turn(test: &TestCodex, text: &str) -> Op { turn_permission_fields(PermissionProfile::Disabled, test.cwd_path()); Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: text.to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/search_tool.rs b/codex-rs/core/tests/suite/search_tool.rs index f99bf595a83..e9ffeb62ef7 100644 --- a/codex-rs/core/tests/suite/search_tool.rs +++ b/codex-rs/core/tests/suite/search_tool.rs @@ -447,7 +447,6 @@ async fn tool_search_returns_deferred_tools_without_follow_up_tool_injection() - .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "Find the calendar create tool".to_string(), text_elements: Vec::new(), }], @@ -862,7 +861,6 @@ async fn tool_search_returns_deferred_dynamic_tool_and_routes_follow_up_call() - .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "Use the automation tool".to_string(), text_elements: Vec::new(), }], @@ -1172,7 +1170,6 @@ async fn tool_search_surfaced_mcp_tool_errors_are_returned_to_model() -> Result< .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "Find the rmcp echo tool and call it.".to_string(), text_elements: Vec::new(), }], @@ -1494,7 +1491,6 @@ async fn tool_search_matches_dynamic_tools_by_name_description_namespace_and_sch .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "Search for the dynamic tool".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/shell_snapshot.rs b/codex-rs/core/tests/suite/shell_snapshot.rs index 7a6b8bfa71e..69db30b21b4 100644 --- a/codex-rs/core/tests/suite/shell_snapshot.rs +++ b/codex-rs/core/tests/suite/shell_snapshot.rs @@ -161,7 +161,6 @@ async fn run_snapshot_command_with_options( codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: "run unified exec with shell snapshot".into(), text_elements: Vec::new(), }], @@ -263,7 +262,6 @@ async fn run_shell_command_snapshot_with_options( codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: "run shell_command with shell snapshot".into(), text_elements: Vec::new(), }], @@ -345,7 +343,6 @@ async fn run_tool_turn_on_harness( codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: prompt.into(), text_elements: Vec::new(), }], @@ -590,7 +587,6 @@ async fn shell_command_snapshot_still_intercepts_apply_patch() -> Result<()> { codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: "apply patch via shell_command with snapshot".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/skill_approval.rs b/codex-rs/core/tests/suite/skill_approval.rs index fde46b4a6b1..2335ba64155 100644 --- a/codex-rs/core/tests/suite/skill_approval.rs +++ b/codex-rs/core/tests/suite/skill_approval.rs @@ -48,7 +48,6 @@ async fn submit_turn_with_policies( test.codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: prompt.to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/skills.rs b/codex-rs/core/tests/suite/skills.rs index 2931b081f37..e21668fa842 100644 --- a/codex-rs/core/tests/suite/skills.rs +++ b/codex-rs/core/tests/suite/skills.rs @@ -77,12 +77,10 @@ async fn user_turn_includes_skill_instructions() -> Result<()> { .submit(Op::UserInput { items: vec![ UserInput::Text { - client_id: None, text: "please use $demo".to_string(), text_elements: Vec::new(), }, UserInput::Skill { - client_id: None, name: "demo".to_string(), path: skill_path.clone(), }, diff --git a/codex-rs/core/tests/suite/sqlite_state.rs b/codex-rs/core/tests/suite/sqlite_state.rs index 260e96ab9d2..8cbc46e1b59 100644 --- a/codex-rs/core/tests/suite/sqlite_state.rs +++ b/codex-rs/core/tests/suite/sqlite_state.rs @@ -468,7 +468,6 @@ async fn mcp_call_marks_thread_memory_mode_polluted_when_configured() -> Result< test.codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: "call the rmcp echo tool".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/stream_error_allows_next_turn.rs b/codex-rs/core/tests/suite/stream_error_allows_next_turn.rs index 56252cf510d..d82692c26c9 100644 --- a/codex-rs/core/tests/suite/stream_error_allows_next_turn.rs +++ b/codex-rs/core/tests/suite/stream_error_allows_next_turn.rs @@ -96,7 +96,6 @@ async fn continue_after_stream_error() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "first message".into(), text_elements: Vec::new(), }], @@ -120,7 +119,6 @@ async fn continue_after_stream_error() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "follow up".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/stream_no_completed.rs b/codex-rs/core/tests/suite/stream_no_completed.rs index 7f549ef852e..471c60db5a9 100644 --- a/codex-rs/core/tests/suite/stream_no_completed.rs +++ b/codex-rs/core/tests/suite/stream_no_completed.rs @@ -78,7 +78,6 @@ async fn retries_on_early_close() { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/subagent_notifications.rs b/codex-rs/core/tests/suite/subagent_notifications.rs index 57c60dfab83..8891f6ffec8 100644 --- a/codex-rs/core/tests/suite/subagent_notifications.rs +++ b/codex-rs/core/tests/suite/subagent_notifications.rs @@ -765,7 +765,6 @@ async fn subagent_stop_replaces_stop_and_skips_internal_subagents() -> Result<() .thread .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: INTERNAL_SUBAGENT_PROMPT.to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/tool_harness.rs b/codex-rs/core/tests/suite/tool_harness.rs index ca06792db76..a6f808b9d79 100644 --- a/codex-rs/core/tests/suite/tool_harness.rs +++ b/codex-rs/core/tests/suite/tool_harness.rs @@ -104,7 +104,6 @@ async fn shell_command_tool_executes_command_and_streams_output() -> anyhow::Res codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: "please run the shell command".into(), text_elements: Vec::new(), }], @@ -187,7 +186,6 @@ async fn update_plan_tool_emits_plan_update_event() -> anyhow::Result<()> { codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: "please update the plan".into(), text_elements: Vec::new(), }], @@ -280,7 +278,6 @@ async fn update_plan_tool_rejects_malformed_payload() -> anyhow::Result<()> { codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: "please update the plan".into(), text_elements: Vec::new(), }], @@ -383,7 +380,6 @@ async fn apply_patch_tool_executes_and_emits_patch_events() -> anyhow::Result<() codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: "please apply a patch".into(), text_elements: Vec::new(), }], @@ -523,7 +519,6 @@ async fn apply_patch_reports_parse_diagnostics() -> anyhow::Result<()> { codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: "please apply a patch".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/tool_parallelism.rs b/codex-rs/core/tests/suite/tool_parallelism.rs index 88baf5f7655..76bb27c4230 100644 --- a/codex-rs/core/tests/suite/tool_parallelism.rs +++ b/codex-rs/core/tests/suite/tool_parallelism.rs @@ -39,7 +39,6 @@ async fn run_turn(test: &TestCodex, prompt: &str) -> anyhow::Result<()> { test.codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: prompt.into(), text_elements: Vec::new(), }], @@ -367,7 +366,6 @@ async fn shell_tools_start_before_response_completed_when_stream_delayed() -> an test.codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: "stream delayed completion".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/truncation.rs b/codex-rs/core/tests/suite/truncation.rs index 7be0fc56fe2..fe078977ffb 100644 --- a/codex-rs/core/tests/suite/truncation.rs +++ b/codex-rs/core/tests/suite/truncation.rs @@ -517,7 +517,6 @@ async fn mcp_image_output_preserves_image_and_no_text_summary() -> Result<()> { .codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: "call the rmcp image tool".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/unified_exec.rs b/codex-rs/core/tests/suite/unified_exec.rs index 52d50eb73f8..ba754b025f3 100644 --- a/codex-rs/core/tests/suite/unified_exec.rs +++ b/codex-rs/core/tests/suite/unified_exec.rs @@ -194,7 +194,6 @@ async fn submit_unified_exec_turn( test.codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: prompt.into(), text_elements: Vec::new(), }], @@ -287,7 +286,6 @@ async fn unified_exec_intercepts_apply_patch_exec_command() -> Result<()> { codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: "apply patch via unified exec".into(), text_elements: Vec::new(), }], @@ -2144,7 +2142,6 @@ async fn unified_exec_keeps_long_running_session_after_turn_end() -> Result<()> codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: "keep unified exec process after turn end".into(), text_elements: Vec::new(), }], @@ -2249,7 +2246,6 @@ async fn unified_exec_interrupt_preserves_long_running_session() -> Result<()> { codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: "interrupt long-running unified exec".into(), text_elements: Vec::new(), }], @@ -2723,7 +2719,6 @@ async fn unified_exec_runs_under_sandbox() -> Result<()> { codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: "summarize large output".into(), text_elements: Vec::new(), }], @@ -2847,7 +2842,6 @@ async fn unified_exec_enforces_glob_deny_read_policy() -> Result<()> { codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: "read the fixture files".into(), text_elements: Vec::new(), }], @@ -2987,7 +2981,6 @@ async fn unified_exec_python_prompt_under_seatbelt() -> Result<()> { codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: "start python under seatbelt".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/user_notification.rs b/codex-rs/core/tests/suite/user_notification.rs index 84c36f5b7c2..054d926b2b8 100644 --- a/codex-rs/core/tests/suite/user_notification.rs +++ b/codex-rs/core/tests/suite/user_notification.rs @@ -59,7 +59,6 @@ mv "${tmp_path}" "${payload_path}""#, .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: "hello world".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/user_shell_cmd.rs b/codex-rs/core/tests/suite/user_shell_cmd.rs index 242849f0d3f..3e2263705ab 100644 --- a/codex-rs/core/tests/suite/user_shell_cmd.rs +++ b/codex-rs/core/tests/suite/user_shell_cmd.rs @@ -176,7 +176,6 @@ async fn user_shell_command_does_not_replace_active_turn() -> anyhow::Result<()> .codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: "run model shell command".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/view_image.rs b/codex-rs/core/tests/suite/view_image.rs index 8ce38eea9ef..e6320ba1a72 100644 --- a/codex-rs/core/tests/suite/view_image.rs +++ b/codex-rs/core/tests/suite/view_image.rs @@ -206,7 +206,6 @@ async fn assert_user_turn_local_image_resizes_to( .submit(disabled_user_turn( &test, vec![UserInput::LocalImage { - client_id: None, path: abs_path.clone(), detail: None, }], @@ -318,7 +317,6 @@ async fn view_image_tool_attaches_local_image() -> anyhow::Result<()> { .submit(disabled_user_turn( &test, vec![UserInput::Text { - client_id: None, text: "please add the screenshot".into(), text_elements: Vec::new(), }], @@ -712,7 +710,6 @@ async fn view_image_tool_can_preserve_original_resolution_when_requested_on_gpt5 .submit(disabled_user_turn( &test, vec![UserInput::Text { - client_id: None, text: "please add the original screenshot".into(), text_elements: Vec::new(), }], @@ -802,7 +799,6 @@ async fn view_image_tool_errors_clearly_for_unsupported_detail_values() -> anyho .submit(disabled_user_turn( &test, vec![UserInput::Text { - client_id: None, text: "please attach the image at low detail".into(), text_elements: Vec::new(), }], @@ -883,7 +879,6 @@ async fn view_image_tool_treats_null_detail_as_omitted() -> anyhow::Result<()> { .submit(disabled_user_turn( &test, vec![UserInput::Text { - client_id: None, text: "please attach the image with a null detail".into(), text_elements: Vec::new(), }], @@ -974,7 +969,6 @@ async fn view_image_tool_resizes_when_model_lacks_original_detail_support() -> a .submit(disabled_user_turn( &test, vec![UserInput::Text { - client_id: None, text: "please add the screenshot".into(), text_elements: Vec::new(), }], @@ -1069,7 +1063,6 @@ async fn view_image_tool_does_not_force_original_resolution_with_capability_only .submit(disabled_user_turn( &test, vec![UserInput::Text { - client_id: None, text: "please add the screenshot".into(), text_elements: Vec::new(), }], @@ -1152,7 +1145,6 @@ async fn view_image_tool_errors_when_path_is_directory() -> anyhow::Result<()> { .submit(disabled_user_turn( &test, vec![UserInput::Text { - client_id: None, text: "please attach the folder".into(), text_elements: Vec::new(), }], @@ -1224,7 +1216,6 @@ async fn view_image_tool_errors_for_non_image_files() -> anyhow::Result<()> { .submit(disabled_user_turn( &test, vec![UserInput::Text { - client_id: None, text: "please use the view_image tool to read the json file".into(), text_elements: Vec::new(), }], @@ -1302,7 +1293,6 @@ async fn view_image_tool_errors_when_file_missing() -> anyhow::Result<()> { .submit(disabled_user_turn( &test, vec![UserInput::Text { - client_id: None, text: "please attach the missing image".into(), text_elements: Vec::new(), }], @@ -1433,7 +1423,6 @@ async fn view_image_tool_returns_unsupported_message_for_text_only_model() -> an .submit(disabled_user_turn( &test, vec![UserInput::Text { - client_id: None, text: "please attach the image".into(), text_elements: Vec::new(), }], @@ -1505,7 +1494,6 @@ async fn replaces_invalid_local_image_after_bad_request() -> anyhow::Result<()> .submit(disabled_user_turn( &test, vec![UserInput::LocalImage { - client_id: None, path: abs_path.clone(), detail: None, }], diff --git a/codex-rs/core/tests/suite/websocket_fallback.rs b/codex-rs/core/tests/suite/websocket_fallback.rs index e8233e73ebb..be33467d6a5 100644 --- a/codex-rs/core/tests/suite/websocket_fallback.rs +++ b/codex-rs/core/tests/suite/websocket_fallback.rs @@ -154,7 +154,6 @@ async fn websocket_fallback_hides_first_websocket_retry_stream_error() -> Result codex .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: "hello".into(), text_elements: Vec::new(), }], diff --git a/codex-rs/core/tests/suite/window_headers.rs b/codex-rs/core/tests/suite/window_headers.rs index f462336da4e..953b075f656 100644 --- a/codex-rs/core/tests/suite/window_headers.rs +++ b/codex-rs/core/tests/suite/window_headers.rs @@ -107,7 +107,6 @@ async fn submit_user_turn(codex: &Arc, text: &str) -> Result<()> { .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: text.to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/debug-client/src/client.rs b/codex-rs/debug-client/src/client.rs index 9f2765dddff..6d0390d3822 100644 --- a/codex-rs/debug-client/src/client.rs +++ b/codex-rs/debug-client/src/client.rs @@ -198,8 +198,8 @@ impl AppServerClient { request_id: request_id.clone(), params: TurnStartParams { thread_id: thread_id.to_string(), + client_user_message_id: None, input: vec![UserInput::Text { - client_id: None, text, // Debug client sends plain text with no UI markup spans. text_elements: Vec::new(), diff --git a/codex-rs/exec/src/lib.rs b/codex-rs/exec/src/lib.rs index cd904fdc051..8d632396b08 100644 --- a/codex-rs/exec/src/lib.rs +++ b/codex-rs/exec/src/lib.rs @@ -631,14 +631,9 @@ async fn run_exec_session(args: ExecRunArgs) -> anyhow::Result<()> { let mut items: Vec = imgs .into_iter() .chain(args.images.iter().cloned()) - .map(|path| UserInput::LocalImage { - client_id: None, - path, - detail: None, - }) + .map(|path| UserInput::LocalImage { path, detail: None }) .collect(); items.push(UserInput::Text { - client_id: None, text: prompt_text.clone(), // CLI input doesn't track UI element ranges, so none are available here. text_elements: Vec::new(), @@ -656,14 +651,9 @@ async fn run_exec_session(args: ExecRunArgs) -> anyhow::Result<()> { let prompt_text = resolve_root_prompt(root_prompt); let mut items: Vec = imgs .into_iter() - .map(|path| UserInput::LocalImage { - client_id: None, - path, - detail: None, - }) + .map(|path| UserInput::LocalImage { path, detail: None }) .collect(); items.push(UserInput::Text { - client_id: None, text: prompt_text.clone(), // CLI input doesn't track UI element ranges, so none are available here. text_elements: Vec::new(), @@ -789,6 +779,7 @@ async fn run_exec_session(args: ExecRunArgs) -> anyhow::Result<()> { request_id: request_ids.next(), params: TurnStartParams { thread_id: primary_thread_id_for_span.clone(), + client_user_message_id: None, input: items.into_iter().map(Into::into).collect(), responsesapi_client_metadata: None, additional_context: None, diff --git a/codex-rs/mcp-server/src/codex_tool_runner.rs b/codex-rs/mcp-server/src/codex_tool_runner.rs index 631641333ad..05b995e7215 100644 --- a/codex-rs/mcp-server/src/codex_tool_runner.rs +++ b/codex-rs/mcp-server/src/codex_tool_runner.rs @@ -105,7 +105,6 @@ pub async fn run_codex_tool_session( op: Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: initial_prompt.clone(), // MCP tool prompts are plain text with no UI element ranges. text_elements: Vec::new(), @@ -115,6 +114,7 @@ pub async fn run_codex_tool_session( additional_context: Default::default(), thread_settings: Default::default(), }, + client_user_message_id: None, trace: None, }; @@ -157,7 +157,6 @@ pub async fn run_codex_tool_session_reply( .submit(Op::UserInput { environments: None, items: vec![UserInput::Text { - client_id: None, text: prompt, // MCP tool prompts are plain text with no UI element ranges. text_elements: Vec::new(), diff --git a/codex-rs/mcp-server/src/message_processor.rs b/codex-rs/mcp-server/src/message_processor.rs index 3123bcf335d..2f85b351435 100644 --- a/codex-rs/mcp-server/src/message_processor.rs +++ b/codex-rs/mcp-server/src/message_processor.rs @@ -539,6 +539,7 @@ impl MessageProcessor { .submit_with_id(Submission { id: request_id_string, op: codex_protocol::protocol::Op::Interrupt, + client_user_message_id: None, trace: None, }) .await diff --git a/codex-rs/memories/write/src/phase2.rs b/codex-rs/memories/write/src/phase2.rs index 9e394bc2582..c78032d9c2a 100644 --- a/codex-rs/memories/write/src/phase2.rs +++ b/codex-rs/memories/write/src/phase2.rs @@ -349,7 +349,6 @@ mod agent { pub(super) fn get_prompt(root: &Path) -> Vec { let prompt = build_consolidation_prompt(root); vec![UserInput::Text { - client_id: None, text: prompt, text_elements: vec![], }] diff --git a/codex-rs/otel/tests/suite/otel_export_routing_policy.rs b/codex-rs/otel/tests/suite/otel_export_routing_policy.rs index 4b5f0da8c27..582d9792c55 100644 --- a/codex-rs/otel/tests/suite/otel_export_routing_policy.rs +++ b/codex-rs/otel/tests/suite/otel_export_routing_policy.rs @@ -134,17 +134,14 @@ fn otel_export_routing_policy_routes_user_prompt_log_and_trace_events() { let _root_guard = root_span.enter(); manager.user_prompt(&[ UserInput::Text { - client_id: None, text: "super secret prompt".to_string(), text_elements: Vec::new(), }, UserInput::Image { - client_id: None, image_url: "https://example.com/image.png".to_string(), detail: None, }, UserInput::LocalImage { - client_id: None, path: PathBuf::from("/tmp/secret.png"), detail: None, }, diff --git a/codex-rs/protocol/src/items.rs b/codex-rs/protocol/src/items.rs index ed616d6c553..bf7be695e70 100644 --- a/codex-rs/protocol/src/items.rs +++ b/codex-rs/protocol/src/items.rs @@ -56,6 +56,9 @@ pub enum TurnItem { #[derive(Debug, Clone, Deserialize, Serialize, TS, JsonSchema)] pub struct UserMessageItem { pub id: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[ts(optional)] + pub client_id: Option, pub content: Vec, } @@ -237,6 +240,7 @@ impl UserMessageItem { pub fn new(content: &[UserInput]) -> Self { Self { id: uuid::Uuid::new_v4().to_string(), + client_id: None, content: content.to_vec(), } } diff --git a/codex-rs/protocol/src/models.rs b/codex-rs/protocol/src/models.rs index aa93d44765d..cbf8895ddbe 100644 --- a/codex-rs/protocol/src/models.rs +++ b/codex-rs/protocol/src/models.rs @@ -2654,7 +2654,6 @@ mod tests { let image_url = "data:image/png;base64,abc".to_string(); let item = ResponseInputItem::from(vec![UserInput::Image { - client_id: None, image_url: image_url.clone(), detail: None, }]); @@ -2678,7 +2677,6 @@ mod tests { let image_url = "data:image/png;base64,abc".to_string(); let item = ResponseInputItem::from(vec![UserInput::Image { - client_id: None, image_url: image_url.clone(), detail: Some(ImageDetail::Original), }]); @@ -2871,12 +2869,10 @@ mod tests { let item = ResponseInputItem::from(vec![ UserInput::Image { - client_id: None, image_url: image_url.clone(), detail: None, }, UserInput::LocalImage { - client_id: None, path: local_path, detail: None, }, @@ -2921,7 +2917,6 @@ mod tests { std::fs::write(&local_path, TINY_PNG_BYTES)?; let item = ResponseInputItem::from(vec![UserInput::LocalImage { - client_id: None, path: local_path, detail: Some(ImageDetail::Original), }]); @@ -2948,7 +2943,6 @@ mod tests { let missing_path = dir.path().join("missing-image.png"); let item = ResponseInputItem::from(vec![UserInput::LocalImage { - client_id: None, path: missing_path.clone(), detail: None, }]); @@ -2984,7 +2978,6 @@ mod tests { std::fs::write(&json_path, br#"{"hello":"world"}"#)?; let item = ResponseInputItem::from(vec![UserInput::LocalImage { - client_id: None, path: json_path.clone(), detail: None, }]); @@ -3023,7 +3016,6 @@ mod tests { )?; let item = ResponseInputItem::from(vec![UserInput::LocalImage { - client_id: None, path: svg_path.clone(), detail: None, }]); diff --git a/codex-rs/protocol/src/protocol.rs b/codex-rs/protocol/src/protocol.rs index 201ed582991..9c31895a7bd 100644 --- a/codex-rs/protocol/src/protocol.rs +++ b/codex-rs/protocol/src/protocol.rs @@ -129,6 +129,9 @@ pub struct Submission { pub id: String, /// Payload pub op: Op, + /// Client-provided id for the user message represented by `Op::UserInput`. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub client_user_message_id: Option, /// Optional W3C trace carrier propagated across async submission handoffs. #[serde(default, skip_serializing_if = "Option::is_none")] pub trace: Option, @@ -5068,7 +5071,6 @@ mod tests { #[test] fn user_input_text_serializes_empty_text_elements() -> Result<()> { let input = UserInput::Text { - client_id: None, text: "hello".to_string(), text_elements: Vec::new(), }; @@ -5136,17 +5138,14 @@ mod tests { let local_path = PathBuf::from("/tmp/local.png"); let item = UserMessageItem::new(&[ crate::user_input::UserInput::Image { - client_id: None, image_url: "https://example.com/first.png".to_string(), detail: Some(ImageDetail::Original), }, crate::user_input::UserInput::Image { - client_id: None, image_url: "https://example.com/second.png".to_string(), detail: None, }, crate::user_input::UserInput::LocalImage { - client_id: None, path: local_path.clone(), detail: Some(ImageDetail::Original), }, diff --git a/codex-rs/protocol/src/user_input.rs b/codex-rs/protocol/src/user_input.rs index b9a50f25aea..ce4cf99eba9 100644 --- a/codex-rs/protocol/src/user_input.rs +++ b/codex-rs/protocol/src/user_input.rs @@ -14,8 +14,6 @@ pub const MAX_USER_INPUT_TEXT_CHARS: usize = 1 << 20; #[serde(tag = "type", rename_all = "snake_case")] pub enum UserInput { Text { - #[serde(default, skip_serializing_if = "Option::is_none")] - client_id: Option, text: String, /// UI-defined spans within `text` that should be treated as special elements. /// These are byte ranges into the UTF-8 `text` buffer and are used to render @@ -26,8 +24,6 @@ pub enum UserInput { }, /// Pre‑encoded data: URI image. Image { - #[serde(default, skip_serializing_if = "Option::is_none")] - client_id: Option, image_url: String, #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional)] @@ -37,8 +33,6 @@ pub enum UserInput { /// Local image path provided by the user. This will be converted to an /// `Image` variant (base64 data URL) during request serialization. LocalImage { - #[serde(default, skip_serializing_if = "Option::is_none")] - client_id: Option, path: std::path::PathBuf, #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional)] @@ -47,8 +41,6 @@ pub enum UserInput { /// Skill selected by the user (name + path to SKILL.md). Skill { - #[serde(default, skip_serializing_if = "Option::is_none")] - client_id: Option, name: String, path: std::path::PathBuf, }, @@ -56,12 +48,7 @@ pub enum UserInput { /// /// `path` identifies the exact mention target, for example /// `app://` or `plugin://@`. - Mention { - #[serde(default, skip_serializing_if = "Option::is_none")] - client_id: Option, - name: String, - path: String, - }, + Mention { name: String, path: String }, } #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, TS, JsonSchema)] diff --git a/codex-rs/thread-manager-sample/src/main.rs b/codex-rs/thread-manager-sample/src/main.rs index 8e3f1c7dbaf..fb407de3e9a 100644 --- a/codex-rs/thread-manager-sample/src/main.rs +++ b/codex-rs/thread-manager-sample/src/main.rs @@ -291,7 +291,6 @@ async fn run_turn(thread: &CodexThread, thread_id: &str, prompt: String) -> anyh thread .submit(Op::UserInput { items: vec![UserInput::Text { - client_id: None, text: prompt, text_elements: Vec::new(), }], diff --git a/codex-rs/tui/src/app/tests.rs b/codex-rs/tui/src/app/tests.rs index 939238e3ecd..3fa71d7b450 100644 --- a/codex-rs/tui/src/app/tests.rs +++ b/codex-rs/tui/src/app/tests.rs @@ -339,8 +339,8 @@ async fn enqueue_primary_thread_session_replays_turns_before_initial_prompt_subm TurnStatus::Completed, vec![ThreadItem::UserMessage { id: "user-1".to_string(), + client_id: None, content: vec![AppServerUserInput::Text { - client_id: None, text: "earlier prompt".to_string(), text_elements: Vec::new(), }], @@ -377,7 +377,6 @@ async fn enqueue_primary_thread_session_replays_turns_before_initial_prompt_subm assert_eq!( submitted_items, Some(vec![UserInput::Text { - client_id: None, text: initial_prompt, text_elements: Vec::new(), }]) @@ -613,7 +612,6 @@ async fn replayed_turn_complete_submits_restored_queued_follow_up() { Op::UserTurn { items, .. } => assert_eq!( items, vec![UserInput::Text { - client_id: None, text: "queued follow-up".to_string(), text_elements: Vec::new(), }] @@ -857,7 +855,6 @@ async fn replay_thread_snapshot_does_not_submit_queue_before_replay_catches_up() Op::UserTurn { items, .. } => assert_eq!( items, vec![UserInput::Text { - client_id: None, text: "queued follow-up".to_string(), text_elements: Vec::new(), }] @@ -915,7 +912,6 @@ async fn replay_thread_snapshot_restores_pending_pastes_for_submit() { Op::UserTurn { items, .. } => assert_eq!( items, vec![UserInput::Text { - client_id: None, text: large, text_elements: Vec::new(), }] @@ -986,7 +982,6 @@ async fn replay_thread_snapshot_restores_collaboration_mode_for_draft_submit() { assert_eq!( items, vec![UserInput::Text { - client_id: None, text: "draft prompt".to_string(), text_elements: Vec::new(), }] @@ -3270,8 +3265,8 @@ async fn side_thread_snapshot_hides_forked_parent_transcript() { TurnStatus::Completed, vec![ThreadItem::UserMessage { id: "parent-user".to_string(), + client_id: None, content: vec![AppServerUserInput::Text { - client_id: None, text: "parent prompt should stay hidden".to_string(), text_elements: Vec::new(), }], @@ -4697,8 +4692,8 @@ async fn replay_thread_snapshot_replays_turn_history_in_order() { items_view: codex_app_server_protocol::TurnItemsView::Full, items: vec![ThreadItem::UserMessage { id: "user-1".to_string(), + client_id: None, content: vec![AppServerUserInput::Text { - client_id: None, text: "first prompt".to_string(), text_elements: Vec::new(), }], @@ -4715,8 +4710,8 @@ async fn replay_thread_snapshot_replays_turn_history_in_order() { items: vec![ ThreadItem::UserMessage { id: "user-2".to_string(), + client_id: None, content: vec![AppServerUserInput::Text { - client_id: None, text: "third prompt".to_string(), text_elements: Vec::new(), }], @@ -4863,8 +4858,8 @@ async fn refreshed_snapshot_session_persists_resumed_turns() { TurnStatus::Completed, vec![ThreadItem::UserMessage { id: "user-1".to_string(), + client_id: None, content: vec![AppServerUserInput::Text { - client_id: None, text: "restored prompt".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/tui/src/app/tests/startup.rs b/codex-rs/tui/src/app/tests/startup.rs index ddf49ff51ab..51d6622e161 100644 --- a/codex-rs/tui/src/app/tests/startup.rs +++ b/codex-rs/tui/src/app/tests/startup.rs @@ -171,7 +171,6 @@ async fn startup_thread_started_submits_queued_startup_input() { Op::UserTurn { items, .. } => assert_eq!( items, vec![UserInput::Text { - client_id: None, text: "queued before startup completes".to_string(), text_elements: Vec::new(), }] diff --git a/codex-rs/tui/src/app_server_session.rs b/codex-rs/tui/src/app_server_session.rs index adb4a43eb8a..2b7b1cd8852 100644 --- a/codex-rs/tui/src/app_server_session.rs +++ b/codex-rs/tui/src/app_server_session.rs @@ -668,6 +668,7 @@ impl AppServerSession { request_id, params: TurnStartParams { thread_id: thread_id.to_string(), + client_user_message_id: None, input: items, responsesapi_client_metadata: None, additional_context: None, @@ -732,6 +733,7 @@ impl AppServerSession { request_id, params: TurnSteerParams { thread_id: thread_id.to_string(), + client_user_message_id: None, input: items, responsesapi_client_metadata: None, additional_context: None, @@ -2264,8 +2266,8 @@ mod tests { items: vec![ codex_app_server_protocol::ThreadItem::UserMessage { id: "user-1".to_string(), + client_id: None, content: vec![codex_app_server_protocol::UserInput::Text { - client_id: None, text: "hello from history".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/tui/src/chatwidget/input_submission.rs b/codex-rs/tui/src/chatwidget/input_submission.rs index 5b596dcf238..4700ffc0cc6 100644 --- a/codex-rs/tui/src/chatwidget/input_submission.rs +++ b/codex-rs/tui/src/chatwidget/input_submission.rs @@ -163,7 +163,6 @@ impl ChatWidget { for image_url in &remote_image_urls { items.push(UserInput::Image { - client_id: None, url: image_url.clone(), detail: None, }); @@ -171,7 +170,6 @@ impl ChatWidget { for image in &local_images { items.push(UserInput::LocalImage { - client_id: None, path: image.path.clone(), detail: None, }); @@ -179,7 +177,6 @@ impl ChatWidget { if !text.is_empty() { items.push(UserInput::Text { - client_id: None, text: text.clone(), text_elements: app_server_text_elements(&text_elements), }); @@ -212,7 +209,6 @@ impl ChatWidget { && selected_skill_paths.insert(skill.path_to_skills_md.clone()) { items.push(UserInput::Skill { - client_id: None, name: skill.name.clone(), path: skill.path_to_skills_md.to_path_buf(), }); @@ -227,7 +223,6 @@ impl ChatWidget { continue; } items.push(UserInput::Skill { - client_id: None, name: skill.name.clone(), path: skill.path_to_skills_md.to_path_buf(), }); @@ -251,7 +246,6 @@ impl ChatWidget { .find(|plugin| plugin.config_name == plugin_config_name) { items.push(UserInput::Mention { - client_id: None, name: plugin.display_name.clone(), path: binding.path.clone(), }); @@ -278,7 +272,6 @@ impl ChatWidget { { selected_app_ids.insert(app_id.to_string()); items.push(UserInput::Mention { - client_id: None, name: app.name.clone(), path: binding.path.clone(), }); @@ -293,7 +286,6 @@ impl ChatWidget { } let app_id = app.id.as_str(); items.push(UserInput::Mention { - client_id: None, name: app.name.clone(), path: format!("app://{app_id}"), }); diff --git a/codex-rs/tui/src/chatwidget/tests/app_server.rs b/codex-rs/tui/src/chatwidget/tests/app_server.rs index 63b613d0c18..f4fcc85e438 100644 --- a/codex-rs/tui/src/chatwidget/tests/app_server.rs +++ b/codex-rs/tui/src/chatwidget/tests/app_server.rs @@ -283,8 +283,8 @@ async fn live_app_server_user_message_item_completed_does_not_duplicate_rendered completed_at_ms: 0, item: AppServerThreadItem::UserMessage { id: "user-1".to_string(), + client_id: None, content: vec![AppServerUserInput::Text { - client_id: None, text: "Hi, are you there?".to_string(), text_elements: Vec::new(), }], diff --git a/codex-rs/tui/src/chatwidget/tests/composer_submission.rs b/codex-rs/tui/src/chatwidget/tests/composer_submission.rs index 67a97f155de..23487af437a 100644 --- a/codex-rs/tui/src/chatwidget/tests/composer_submission.rs +++ b/codex-rs/tui/src/chatwidget/tests/composer_submission.rs @@ -60,7 +60,6 @@ async fn submission_preserves_text_elements_and_local_images() { assert_eq!( items[0], UserInput::LocalImage { - client_id: None, path: local_images[0].clone(), detail: None, } @@ -68,7 +67,6 @@ async fn submission_preserves_text_elements_and_local_images() { assert_eq!( items[1], UserInput::Text { - client_id: None, text: text.clone(), text_elements: text_elements.clone().into_iter().map(Into::into).collect(), } @@ -273,7 +271,6 @@ async fn submission_with_remote_and_local_images_keeps_local_placeholder_numberi assert_eq!( items[0], UserInput::Image { - client_id: None, url: remote_url.clone(), detail: None, } @@ -281,7 +278,6 @@ async fn submission_with_remote_and_local_images_keeps_local_placeholder_numberi assert_eq!( items[1], UserInput::LocalImage { - client_id: None, path: local_images[0].clone(), detail: None, } @@ -289,7 +285,6 @@ async fn submission_with_remote_and_local_images_keeps_local_placeholder_numberi assert_eq!( items[2], UserInput::Text { - client_id: None, text: text.clone(), text_elements: text_elements.clone().into_iter().map(Into::into).collect(), } @@ -363,7 +358,6 @@ async fn enter_with_only_remote_images_submits_user_turn() { assert_eq!( items, vec![UserInput::Image { - client_id: None, url: remote_url.clone(), detail: None, }] @@ -1254,7 +1248,6 @@ async fn submit_user_message_ignores_inaccessible_app_mentions_from_bindings() { assert_eq!( items, vec![UserInput::Text { - client_id: None, text: "$arabica-uae".to_string(), text_elements: Vec::new(), }] @@ -1266,32 +1259,26 @@ fn user_message_display_from_inputs_matches_flattened_user_message_shape() { let local_image = PathBuf::from("/tmp/local.png"); let rendered = ChatWidget::user_message_display_from_inputs(&[ UserInput::Text { - client_id: None, text: "hello ".to_string(), text_elements: vec![TextElement::new((0..5).into(), /*placeholder*/ None).into()], }, UserInput::Image { - client_id: None, url: "https://example.com/remote.png".to_string(), detail: None, }, UserInput::LocalImage { - client_id: None, path: local_image.clone(), detail: None, }, UserInput::Skill { - client_id: None, name: "demo".to_string(), path: PathBuf::from("/tmp/skill/SKILL.md"), }, UserInput::Mention { - client_id: None, name: "repo".to_string(), path: "app://repo".to_string(), }, UserInput::Text { - client_id: None, text: "world".to_string(), text_elements: vec![TextElement::new((0..5).into(), Some("planet".to_string())).into()], }, @@ -1316,7 +1303,6 @@ fn user_message_display_from_inputs_hides_prompt_context() { let raw_message = "# Context from my IDE setup:\n\n## Active file: src/lib.rs\n\n## My request for Codex:\nAsk $figma"; let mention_start = raw_message.find("$figma").expect("mention in raw message"); let rendered = ChatWidget::user_message_display_from_inputs(&[UserInput::Text { - client_id: None, text: raw_message.to_string(), text_elements: vec![ TextElement::new( @@ -1350,12 +1336,10 @@ async fn committed_user_message_with_hidden_prompt_context_renders_local_images( "user-1", vec![ UserInput::Text { - client_id: None, text: raw_message.to_string(), text_elements: Vec::new(), }, UserInput::LocalImage { - client_id: None, path: local_image.clone(), detail: None, }, diff --git a/codex-rs/tui/src/chatwidget/tests/exec_flow.rs b/codex-rs/tui/src/chatwidget/tests/exec_flow.rs index b4956fe7303..8dbace43784 100644 --- a/codex-rs/tui/src/chatwidget/tests/exec_flow.rs +++ b/codex-rs/tui/src/chatwidget/tests/exec_flow.rs @@ -1035,7 +1035,6 @@ async fn user_message_during_user_shell_command_is_queued_not_steered() { Op::UserTurn { items, .. } => assert_eq!( items, vec![UserInput::Text { - client_id: None, text: "hi".to_string(), text_elements: Vec::new(), }] diff --git a/codex-rs/tui/src/chatwidget/tests/goal_validation.rs b/codex-rs/tui/src/chatwidget/tests/goal_validation.rs index 373977867bb..b186cdf7ff6 100644 --- a/codex-rs/tui/src/chatwidget/tests/goal_validation.rs +++ b/codex-rs/tui/src/chatwidget/tests/goal_validation.rs @@ -218,7 +218,6 @@ async fn queued_goal_slash_command_rejects_oversized_objective_and_drains_next_i Op::UserTurn { items, .. } => assert_eq!( items, vec![UserInput::Text { - client_id: None, text: "continue".to_string(), text_elements: Vec::new(), }] diff --git a/codex-rs/tui/src/chatwidget/tests/helpers.rs b/codex-rs/tui/src/chatwidget/tests/helpers.rs index 1ef06d5e47c..c63b617ab34 100644 --- a/codex-rs/tui/src/chatwidget/tests/helpers.rs +++ b/codex-rs/tui/src/chatwidget/tests/helpers.rs @@ -722,6 +722,7 @@ pub(super) fn replay_user_message_inputs( chat.replay_thread_item( AppServerThreadItem::UserMessage { id: item_id.to_string(), + client_id: None, content, }, "turn-1".to_string(), @@ -739,7 +740,6 @@ pub(super) fn replay_user_message_text( chat, item_id, vec![AppServerUserInput::Text { - client_id: None, text: text.into(), text_elements: Vec::new(), }], @@ -929,7 +929,6 @@ pub(super) fn complete_user_message(chat: &mut ChatWidget, item_id: &str, text: chat, item_id, vec![UserInput::Text { - client_id: None, text: text.to_string(), text_elements: Vec::new(), }], @@ -948,6 +947,7 @@ pub(super) fn complete_user_message_for_inputs( completed_at_ms: 0, item: AppServerThreadItem::UserMessage { id: item_id.to_string(), + client_id: None, content, }, }), diff --git a/codex-rs/tui/src/chatwidget/tests/history_replay.rs b/codex-rs/tui/src/chatwidget/tests/history_replay.rs index 5bcee614a94..58f99231f3f 100644 --- a/codex-rs/tui/src/chatwidget/tests/history_replay.rs +++ b/codex-rs/tui/src/chatwidget/tests/history_replay.rs @@ -117,12 +117,10 @@ async fn replayed_user_message_preserves_text_elements_and_local_images() { "user-1", vec![ AppServerUserInput::Text { - client_id: None, text: message.clone(), text_elements: text_elements.clone().into_iter().map(Into::into).collect(), }, AppServerUserInput::LocalImage { - client_id: None, path: local_images[0].clone(), detail: None, }, @@ -191,12 +189,10 @@ async fn replayed_user_message_preserves_remote_image_urls() { "user-1", vec![ AppServerUserInput::Text { - client_id: None, text: message.clone(), text_elements: Vec::new(), }, AppServerUserInput::Image { - client_id: None, url: remote_image_urls[0].clone(), detail: None, }, @@ -455,7 +451,6 @@ async fn replayed_user_message_with_only_remote_images_renders_history_cell() { &mut chat, "user-1", vec![AppServerUserInput::Image { - client_id: None, url: remote_image_urls[0].clone(), detail: None, }], @@ -514,7 +509,6 @@ async fn replayed_user_message_with_only_local_images_renders_history_cell() { &mut chat, "user-1", vec![AppServerUserInput::LocalImage { - client_id: None, path: local_images[0].clone(), detail: None, }], diff --git a/codex-rs/tui/src/chatwidget/tests/plan_mode.rs b/codex-rs/tui/src/chatwidget/tests/plan_mode.rs index 2c345cd4944..5fc2fa2ce92 100644 --- a/codex-rs/tui/src/chatwidget/tests/plan_mode.rs +++ b/codex-rs/tui/src/chatwidget/tests/plan_mode.rs @@ -997,7 +997,6 @@ async fn plan_implementation_popup_skips_when_steer_follows_proposed_plan() { Op::UserTurn { items, .. } => assert_eq!( items, vec![UserInput::Text { - client_id: None, text: "Please continue.".to_string(), text_elements: Vec::new(), }] @@ -1040,7 +1039,6 @@ async fn plan_implementation_popup_shows_after_new_plan_follows_steer() { Op::UserTurn { items, .. } => assert_eq!( items, vec![UserInput::Text { - client_id: None, text: "Please revise.".to_string(), text_elements: Vec::new(), }] @@ -1151,7 +1149,6 @@ async fn submit_user_message_queues_while_compaction_turn_is_running() { Op::UserTurn { items, .. } => assert_eq!( items, vec![UserInput::Text { - client_id: None, text: "queued while compacting".to_string(), text_elements: Vec::new(), }] @@ -1194,7 +1191,6 @@ async fn submit_user_message_queues_while_compaction_turn_is_running() { Op::UserTurn { items, .. } => assert_eq!( items, vec![UserInput::Text { - client_id: None, text: "queued while compacting".to_string(), text_elements: Vec::new(), }] @@ -1260,12 +1256,10 @@ async fn submit_user_message_emits_structured_plugin_mentions_from_bindings() { items, vec![ UserInput::Text { - client_id: None, text: "$sample".to_string(), text_elements: Vec::new(), }, UserInput::Mention { - client_id: None, name: "Sample Plugin".to_string(), path: "plugin://sample@test".to_string(), }, @@ -1435,7 +1429,6 @@ async fn plan_slash_command_with_args_submits_prompt_in_plan_mode() { assert_eq!( items[0], UserInput::Text { - client_id: None, text: "build the plan".to_string(), text_elements: Vec::new(), } diff --git a/codex-rs/tui/src/chatwidget/tests/review_mode.rs b/codex-rs/tui/src/chatwidget/tests/review_mode.rs index c762611372a..945f9158b07 100644 --- a/codex-rs/tui/src/chatwidget/tests/review_mode.rs +++ b/codex-rs/tui/src/chatwidget/tests/review_mode.rs @@ -182,8 +182,8 @@ async fn live_app_server_review_prompt_item_is_not_rendered() { completed_at_ms: 0, item: AppServerThreadItem::UserMessage { id: "review-prompt".to_string(), + client_id: None, content: vec![AppServerUserInput::Text { - client_id: None, text: "Review the code changes against the base branch 'main'.".to_string(), text_elements: Vec::new(), }], @@ -214,7 +214,6 @@ async fn steer_rejection_queues_review_follow_up_before_existing_queued_messages Op::UserTurn { items, .. } => assert_eq!( items, vec![UserInput::Text { - client_id: None, text: "review follow-up one".to_string(), text_elements: Vec::new(), }] @@ -225,7 +224,6 @@ async fn steer_rejection_queues_review_follow_up_before_existing_queued_messages Op::UserTurn { items, .. } => assert_eq!( items, vec![UserInput::Text { - client_id: None, text: "review follow-up two".to_string(), text_elements: Vec::new(), }] @@ -266,7 +264,6 @@ async fn steer_rejection_queues_review_follow_up_before_existing_queued_messages Op::UserTurn { items, .. } => assert_eq!( items, vec![UserInput::Text { - client_id: None, text: "review follow-up one\nreview follow-up two".to_string(), text_elements: Vec::new(), }] @@ -280,7 +277,6 @@ async fn steer_rejection_queues_review_follow_up_before_existing_queued_messages Op::UserTurn { items, .. } => assert_eq!( items, vec![UserInput::Text { - client_id: None, text: "queued later".to_string(), text_elements: Vec::new(), }] @@ -601,12 +597,10 @@ async fn item_completed_pops_pending_steer_with_local_image_and_text_elements() "user-1", vec![ UserInput::Image { - client_id: None, url: "data:image/png;base64,placeholder".to_string(), detail: None, }, UserInput::Text { - client_id: None, text, text_elements: Vec::new(), }, @@ -686,7 +680,6 @@ async fn steer_enter_during_final_stream_preserves_follow_up_prompts_in_order() assert_eq!( first_items, vec![UserInput::Text { - client_id: None, text: "first follow-up".to_string(), text_elements: Vec::new(), }] @@ -698,7 +691,6 @@ async fn steer_enter_during_final_stream_preserves_follow_up_prompts_in_order() assert_eq!( second_items, vec![UserInput::Text { - client_id: None, text: "second follow-up".to_string(), text_elements: Vec::new(), }] @@ -752,7 +744,6 @@ async fn manual_interrupt_restores_pending_steers_to_composer() { Op::UserTurn { items, .. } => assert_eq!( items, vec![UserInput::Text { - client_id: None, text: "queued while streaming".to_string(), text_elements: Vec::new(), }] @@ -789,7 +780,6 @@ async fn esc_interrupt_sends_all_pending_steers_immediately_and_keeps_existing_d Op::UserTurn { items, .. } => assert_eq!( items, vec![UserInput::Text { - client_id: None, text: "first pending steer".to_string(), text_elements: Vec::new(), }] @@ -805,7 +795,6 @@ async fn esc_interrupt_sends_all_pending_steers_immediately_and_keeps_existing_d Op::UserTurn { items, .. } => assert_eq!( items, vec![UserInput::Text { - client_id: None, text: "second pending steer".to_string(), text_elements: Vec::new(), }] @@ -829,7 +818,6 @@ async fn esc_interrupt_sends_all_pending_steers_immediately_and_keeps_existing_d Op::UserTurn { items, .. } => assert_eq!( items, vec![UserInput::Text { - client_id: None, text: "first pending steer\nsecond pending steer".to_string(), text_elements: Vec::new(), }] @@ -906,7 +894,6 @@ async fn manual_interrupt_restores_pending_steer_mention_bindings_to_composer() Op::UserTurn { items, .. } => assert_eq!( items, vec![UserInput::Text { - client_id: None, text: "please use $figma".to_string(), text_elements: vec![ TextElement::new((11..17).into(), Some("$figma".to_string())).into() @@ -946,7 +933,6 @@ async fn manual_interrupt_restores_pending_steers_before_queued_messages() { Op::UserTurn { items, .. } => assert_eq!( items, vec![UserInput::Text { - client_id: None, text: "pending steer".to_string(), text_elements: Vec::new(), }] @@ -1440,7 +1426,6 @@ async fn enter_submits_steer_while_review_is_running() { Op::UserTurn { items, .. } => assert_eq!( items, vec![UserInput::Text { - client_id: None, text: "Steer submitted while /review was running.".to_string(), text_elements: Vec::new(), }] diff --git a/codex-rs/tui/src/chatwidget/tests/side.rs b/codex-rs/tui/src/chatwidget/tests/side.rs index f36c1e4b3ff..39a0cb9d733 100644 --- a/codex-rs/tui/src/chatwidget/tests/side.rs +++ b/codex-rs/tui/src/chatwidget/tests/side.rs @@ -237,7 +237,6 @@ async fn submit_user_message_as_plain_user_turn_does_not_run_shell_commands() { Op::UserTurn { items, .. } => assert_eq!( items, vec![UserInput::Text { - client_id: None, text: "!echo hello".to_string(), text_elements: Vec::new(), }] diff --git a/codex-rs/tui/src/chatwidget/tests/slash_commands.rs b/codex-rs/tui/src/chatwidget/tests/slash_commands.rs index 86520668c34..0346b970698 100644 --- a/codex-rs/tui/src/chatwidget/tests/slash_commands.rs +++ b/codex-rs/tui/src/chatwidget/tests/slash_commands.rs @@ -267,7 +267,6 @@ async fn queued_empty_bang_shell_reports_help_when_dequeued_and_drains_next_inpu Op::UserTurn { items, .. } => assert_eq!( items, vec![UserInput::Text { - client_id: None, text: "hello after help".to_string(), text_elements: Vec::new(), }] @@ -307,7 +306,6 @@ async fn queued_bang_shell_waits_for_user_shell_completion_before_next_input() { Op::UserTurn { items, .. } => assert_eq!( items, vec![UserInput::Text { - client_id: None, text: "hello after shell".to_string(), text_elements: Vec::new(), }] @@ -341,7 +339,6 @@ async fn assert_cancelled_queued_menu_drains_next_input(command: &str, expected_ Op::UserTurn { items, .. } => assert_eq!( items, vec![UserInput::Text { - client_id: None, text: "hello after menu".to_string(), text_elements: Vec::new(), }] @@ -381,7 +378,6 @@ async fn queued_slash_menu_selection_drains_next_input() { Op::UserTurn { items, .. } => assert_eq!( items, vec![UserInput::Text { - client_id: None, text: "hello after selection".to_string(), text_elements: Vec::new(), }] @@ -433,7 +429,6 @@ async fn queued_bare_rename_drains_next_input_after_name_update() { Op::UserTurn { items, .. } => assert_eq!( items, vec![UserInput::Text { - client_id: None, text: "hello after rename".to_string(), text_elements: Vec::new(), }] @@ -469,7 +464,6 @@ async fn queued_inline_rename_does_not_drain_again_before_turn_started() { Op::UserTurn { items, .. } => assert_eq!( items, vec![UserInput::Text { - client_id: None, text: "first after rename".to_string(), text_elements: Vec::new(), }] @@ -518,7 +512,6 @@ async fn queued_inline_rename_does_not_drain_again_before_turn_started() { Op::UserTurn { items, .. } => assert_eq!( items, vec![UserInput::Text { - client_id: None, text: "second after rename".to_string(), text_elements: Vec::new(), }] @@ -2234,7 +2227,6 @@ async fn queued_fast_slash_applies_before_next_queued_message() { } if service_tier == ServiceTier::Fast.request_value() => assert_eq!( items, vec![UserInput::Text { - client_id: None, text: "hello after fast".to_string(), text_elements: Vec::new(), }] diff --git a/codex-rs/tui/src/chatwidget/tests/status_and_layout.rs b/codex-rs/tui/src/chatwidget/tests/status_and_layout.rs index 910408d3fc0..acb1df6c5ab 100644 --- a/codex-rs/tui/src/chatwidget/tests/status_and_layout.rs +++ b/codex-rs/tui/src/chatwidget/tests/status_and_layout.rs @@ -1448,7 +1448,6 @@ async fn final_answer_completion_restores_status_indicator_for_pending_steer() { assert_eq!( items, vec![UserInput::Text { - client_id: None, text: "Please summarize the rest more briefly.".to_string(), text_elements: Vec::new(), }] diff --git a/codex-rs/tui/src/ide_context/prompt.rs b/codex-rs/tui/src/ide_context/prompt.rs index 43b1668e92e..884ebfb646b 100644 --- a/codex-rs/tui/src/ide_context/prompt.rs +++ b/codex-rs/tui/src/ide_context/prompt.rs @@ -33,13 +33,11 @@ pub(crate) fn apply_ide_context_to_user_input( let item = std::mem::replace( &mut items[text_index], UserInput::Text { - client_id: None, text: String::new(), text_elements: Vec::new(), }, ); let UserInput::Text { - client_id: None, text, text_elements, } = item @@ -51,7 +49,6 @@ pub(crate) fn apply_ide_context_to_user_input( items.insert( 0, UserInput::Text { - client_id: None, text: prefix, text_elements: Vec::new(), }, @@ -79,7 +76,6 @@ pub(crate) fn extract_prompt_request_with_offset(message: &str) -> (&str, usize) fn prefixed_text_input(prefix: String, text: String, text_elements: Vec) -> UserInput { let prefix_len = prefix.len(); UserInput::Text { - client_id: None, text: format!("{prefix}{text}"), text_elements: text_elements .into_iter() @@ -272,12 +268,10 @@ mod tests { let text = "Ask $figma".to_string(); let mut items = vec![ UserInput::LocalImage { - client_id: None, path: PathBuf::from("/tmp/screenshot.png"), detail: None, }, UserInput::Text { - client_id: None, text, text_elements: vec![TextElement::new( ByteRange { start: 4, end: 10 }, @@ -294,12 +288,10 @@ mod tests { items, vec![ UserInput::LocalImage { - client_id: None, path: PathBuf::from("/tmp/screenshot.png"), detail: None, }, UserInput::Text { - client_id: None, text: format!("{expected_prefix}Ask $figma"), text_elements: vec![TextElement::new( ByteRange { diff --git a/codex-rs/tui/src/resume_picker.rs b/codex-rs/tui/src/resume_picker.rs index b98a06dac3a..2d8619d5390 100644 --- a/codex-rs/tui/src/resume_picker.rs +++ b/codex-rs/tui/src/resume_picker.rs @@ -5781,8 +5781,8 @@ session_picker_view = "dense" items: vec![ ThreadItem::UserMessage { id: String::from("user-1"), + client_id: None, content: vec![codex_app_server_protocol::UserInput::Text { - client_id: None, text: String::from("hello from user"), text_elements: Vec::new(), }], diff --git a/codex-rs/tui/src/resume_picker/transcript.rs b/codex-rs/tui/src/resume_picker/transcript.rs index 5dbc7707c28..abf13bd144c 100644 --- a/codex-rs/tui/src/resume_picker/transcript.rs +++ b/codex-rs/tui/src/resume_picker/transcript.rs @@ -45,9 +45,14 @@ pub(crate) fn thread_to_transcript_cells( let mut cells: TranscriptCells = Vec::new(); for item in thread.turns.iter().flat_map(|turn| turn.items.iter()) { match item { - ThreadItem::UserMessage { id, content } => { + ThreadItem::UserMessage { + id, + client_id, + content, + } => { let item = UserMessageItem { id: id.clone(), + client_id: client_id.clone(), content: content .iter() .cloned() From e26178aefe6e9bda98d0b7a8a4a4ee07cd07be6c Mon Sep 17 00:00:00 2001 From: Alexi Christakis <167903946+alexi-openai@users.noreply.github.com> Date: Thu, 28 May 2026 00:22:51 -0700 Subject: [PATCH 4/6] Fix user input handler test call --- codex-rs/core/src/session/tests.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/codex-rs/core/src/session/tests.rs b/codex-rs/core/src/session/tests.rs index d742a7f8828..b80e0f64eee 100644 --- a/codex-rs/core/src/session/tests.rs +++ b/codex-rs/core/src/session/tests.rs @@ -5577,6 +5577,7 @@ async fn user_turn_updates_approvals_reviewer() { ..Default::default() }, }, + /*client_user_message_id*/ None, ) .await; From 7db92833e4e21db14d486e1940aabecb2e8fcccc Mon Sep 17 00:00:00 2001 From: Alexi Christakis <167903946+alexi-openai@users.noreply.github.com> Date: Thu, 28 May 2026 00:36:33 -0700 Subject: [PATCH 5/6] Update analytics turn steer test params --- codex-rs/analytics/src/analytics_client_tests.rs | 1 + codex-rs/analytics/src/client_tests.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/codex-rs/analytics/src/analytics_client_tests.rs b/codex-rs/analytics/src/analytics_client_tests.rs index ca6365f5578..6a47ec78cae 100644 --- a/codex-rs/analytics/src/analytics_client_tests.rs +++ b/codex-rs/analytics/src/analytics_client_tests.rs @@ -392,6 +392,7 @@ fn sample_turn_steer_request( params: TurnSteerParams { thread_id: thread_id.to_string(), expected_turn_id: expected_turn_id.to_string(), + client_user_message_id: None, input: vec![ UserInput::Text { text: "more".to_string(), diff --git a/codex-rs/analytics/src/client_tests.rs b/codex-rs/analytics/src/client_tests.rs index 68e8a136ad4..4a375b06395 100644 --- a/codex-rs/analytics/src/client_tests.rs +++ b/codex-rs/analytics/src/client_tests.rs @@ -102,6 +102,7 @@ fn sample_turn_steer_request() -> ClientRequest { params: TurnSteerParams { thread_id: "thread-1".to_string(), expected_turn_id: "turn-1".to_string(), + client_user_message_id: None, input: Vec::new(), responsesapi_client_metadata: None, additional_context: None, From 6bb00327515fa295dae7036195b5f1ddbda53c56 Mon Sep 17 00:00:00 2001 From: Alexi Christakis <167903946+alexi-openai@users.noreply.github.com> Date: Thu, 28 May 2026 10:57:53 -0700 Subject: [PATCH 6/6] Persist user message client IDs in history --- .../schema/json/ServerNotification.json | 1 - .../codex_app_server_protocol.schemas.json | 1 - .../codex_app_server_protocol.v2.schemas.json | 1 - .../json/v2/ItemCompletedNotification.json | 1 - .../json/v2/ItemStartedNotification.json | 1 - .../schema/json/v2/ReviewStartResponse.json | 1 - .../schema/json/v2/ThreadForkResponse.json | 1 - .../schema/json/v2/ThreadListResponse.json | 1 - .../json/v2/ThreadMetadataUpdateResponse.json | 1 - .../schema/json/v2/ThreadReadResponse.json | 1 - .../schema/json/v2/ThreadResumeResponse.json | 1 - .../json/v2/ThreadRollbackResponse.json | 1 - .../schema/json/v2/ThreadStartResponse.json | 1 - .../json/v2/ThreadStartedNotification.json | 1 - .../json/v2/ThreadUnarchiveResponse.json | 1 - .../json/v2/TurnCompletedNotification.json | 1 - .../schema/json/v2/TurnStartResponse.json | 1 - .../json/v2/TurnStartedNotification.json | 1 - .../src/protocol/thread_history.rs | 125 +++++++----------- .../src/protocol/v2/item.rs | 1 - .../src/protocol/v2/tests.rs | 56 -------- .../app-server/src/bespoke_event_handling.rs | 2 + .../thread_processor_tests.rs | 1 + .../request_processors/token_usage_replay.rs | 2 + .../app-server/tests/suite/v2/thread_read.rs | 1 + .../core/src/personality_migration_tests.rs | 1 + .../session/rollout_reconstruction_tests.rs | 23 ++++ codex-rs/core/src/session/tests.rs | 10 ++ codex-rs/core/src/thread_manager_tests.rs | 2 + .../core/tests/suite/personality_migration.rs | 1 + codex-rs/core/tests/suite/resume_warning.rs | 1 + codex-rs/core/tests/suite/sqlite_state.rs | 1 + .../external-agent-sessions/src/export.rs | 1 + codex-rs/protocol/src/items.rs | 1 + codex-rs/protocol/src/protocol.rs | 7 +- codex-rs/rollout/src/recorder_tests.rs | 2 + codex-rs/rollout/src/tests.rs | 1 + codex-rs/state/src/extract.rs | 4 + codex-rs/thread-store/src/local/mod.rs | 1 + .../thread-store/src/thread_metadata_sync.rs | 1 + 40 files changed, 113 insertions(+), 150 deletions(-) diff --git a/codex-rs/app-server-protocol/schema/json/ServerNotification.json b/codex-rs/app-server-protocol/schema/json/ServerNotification.json index 2f62d8497ba..b0501b0e9ef 100644 --- a/codex-rs/app-server-protocol/schema/json/ServerNotification.json +++ b/codex-rs/app-server-protocol/schema/json/ServerNotification.json @@ -3562,7 +3562,6 @@ { "properties": { "clientId": { - "default": null, "type": [ "string", "null" diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json index 877b1f9032f..80c2053a5ea 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -15997,7 +15997,6 @@ { "properties": { "clientId": { - "default": null, "type": [ "string", "null" diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json index d14d48cce24..8fc70ab7345 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json @@ -13821,7 +13821,6 @@ { "properties": { "clientId": { - "default": null, "type": [ "string", "null" diff --git a/codex-rs/app-server-protocol/schema/json/v2/ItemCompletedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/ItemCompletedNotification.json index 6bff6581e93..8ba25f9a07d 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ItemCompletedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ItemCompletedNotification.json @@ -501,7 +501,6 @@ { "properties": { "clientId": { - "default": null, "type": [ "string", "null" diff --git a/codex-rs/app-server-protocol/schema/json/v2/ItemStartedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/ItemStartedNotification.json index 7ab80c69cde..70fc43f7b8a 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ItemStartedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ItemStartedNotification.json @@ -501,7 +501,6 @@ { "properties": { "clientId": { - "default": null, "type": [ "string", "null" diff --git a/codex-rs/app-server-protocol/schema/json/v2/ReviewStartResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ReviewStartResponse.json index e676366feac..a644ce8c4e6 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ReviewStartResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ReviewStartResponse.json @@ -645,7 +645,6 @@ { "properties": { "clientId": { - "default": null, "type": [ "string", "null" diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json index ac9c2aceece..ec46130357c 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json @@ -1122,7 +1122,6 @@ { "properties": { "clientId": { - "default": null, "type": [ "string", "null" diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json index 2fa4c4df544..5ace3f7af76 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json @@ -937,7 +937,6 @@ { "properties": { "clientId": { - "default": null, "type": [ "string", "null" diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json index 4d4dd75bd87..a4cc7d91ff7 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json @@ -937,7 +937,6 @@ { "properties": { "clientId": { - "default": null, "type": [ "string", "null" diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json index f9b52825989..234e0867f88 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json @@ -937,7 +937,6 @@ { "properties": { "clientId": { - "default": null, "type": [ "string", "null" diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json index 17a82d3cff8..59ed236c480 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json @@ -1122,7 +1122,6 @@ { "properties": { "clientId": { - "default": null, "type": [ "string", "null" diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json index 51bd35e1803..7815f261c0a 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json @@ -937,7 +937,6 @@ { "properties": { "clientId": { - "default": null, "type": [ "string", "null" diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json index 6488c33e2eb..ca52f10ca53 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json @@ -1122,7 +1122,6 @@ { "properties": { "clientId": { - "default": null, "type": [ "string", "null" diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json index 7207f1a0f95..f6cb72b6b20 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json @@ -937,7 +937,6 @@ { "properties": { "clientId": { - "default": null, "type": [ "string", "null" diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json index 7dadfbb7c74..e4e317e608b 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json @@ -937,7 +937,6 @@ { "properties": { "clientId": { - "default": null, "type": [ "string", "null" diff --git a/codex-rs/app-server-protocol/schema/json/v2/TurnCompletedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/TurnCompletedNotification.json index e59261e252f..6c64eb32753 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/TurnCompletedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnCompletedNotification.json @@ -645,7 +645,6 @@ { "properties": { "clientId": { - "default": null, "type": [ "string", "null" diff --git a/codex-rs/app-server-protocol/schema/json/v2/TurnStartResponse.json b/codex-rs/app-server-protocol/schema/json/v2/TurnStartResponse.json index 40bce29ea10..5fe97545762 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/TurnStartResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnStartResponse.json @@ -645,7 +645,6 @@ { "properties": { "clientId": { - "default": null, "type": [ "string", "null" diff --git a/codex-rs/app-server-protocol/schema/json/v2/TurnStartedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/TurnStartedNotification.json index 5ad0bbf0a87..1db14972182 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/TurnStartedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnStartedNotification.json @@ -645,7 +645,6 @@ { "properties": { "clientId": { - "default": null, "type": [ "string", "null" diff --git a/codex-rs/app-server-protocol/src/protocol/thread_history.rs b/codex-rs/app-server-protocol/src/protocol/thread_history.rs index 66b839ee88b..aa65f9ab9d8 100644 --- a/codex-rs/app-server-protocol/src/protocol/thread_history.rs +++ b/codex-rs/app-server-protocol/src/protocol/thread_history.rs @@ -280,7 +280,7 @@ impl ThreadHistoryBuilder { let content = self.build_user_inputs(payload); turn.items.push(ThreadItem::UserMessage { id, - client_id: None, + client_id: payload.client_id.clone(), content, }); self.current_turn = Some(turn); @@ -356,10 +356,8 @@ impl ThreadHistoryBuilder { ThreadItem::from(payload.item.clone()), ); } - codex_protocol::items::TurnItem::UserMessage(user_message) => { - self.merge_user_message_client_id(&payload.turn_id, user_message); - } - codex_protocol::items::TurnItem::HookPrompt(_) + codex_protocol::items::TurnItem::UserMessage(_) + | codex_protocol::items::TurnItem::HookPrompt(_) | codex_protocol::items::TurnItem::AgentMessage(_) | codex_protocol::items::TurnItem::Reasoning(_) | codex_protocol::items::TurnItem::WebSearch(_) @@ -382,10 +380,8 @@ impl ThreadHistoryBuilder { ThreadItem::from(payload.item.clone()), ); } - codex_protocol::items::TurnItem::UserMessage(user_message) => { - self.merge_user_message_client_id(&payload.turn_id, user_message); - } - codex_protocol::items::TurnItem::HookPrompt(_) + codex_protocol::items::TurnItem::UserMessage(_) + | codex_protocol::items::TurnItem::HookPrompt(_) | codex_protocol::items::TurnItem::AgentMessage(_) | codex_protocol::items::TurnItem::Reasoning(_) | codex_protocol::items::TurnItem::WebSearch(_) @@ -1067,42 +1063,6 @@ impl ThreadHistoryBuilder { upsert_turn_item(&mut turn.items, item); } - fn merge_user_message_client_id( - &mut self, - turn_id: &str, - user_message: &codex_protocol::items::UserMessageItem, - ) { - let Some(client_id) = user_message.client_id.as_deref() else { - return; - }; - let content = user_message - .content - .iter() - .cloned() - .map(UserInput::from) - .collect::>(); - - if let Some(turn) = self.current_turn.as_mut() - && turn.id == turn_id - && set_user_message_client_id(&mut turn.items, &content, client_id) - { - return; - } - - if let Some(turn) = self.turns.iter_mut().find(|turn| turn.id == turn_id) - && set_user_message_client_id(&mut turn.items, &content, client_id) - { - return; - } - - self.upsert_item_in_turn_id( - turn_id, - ThreadItem::from(codex_protocol::items::TurnItem::UserMessage( - user_message.clone(), - )), - ); - } - fn next_item_id(&mut self) -> String { let id = format!("item-{}", self.next_item_index); self.next_item_index += 1; @@ -1179,27 +1139,6 @@ fn upsert_turn_item(items: &mut Vec, item: ThreadItem) { items.push(item); } -fn set_user_message_client_id( - items: &mut [ThreadItem], - content: &[UserInput], - client_id: &str, -) -> bool { - for item in items.iter_mut().rev() { - if let ThreadItem::UserMessage { - client_id: existing_client_id, - content: existing_content, - .. - } = item - && existing_client_id.is_none() - && existing_content == content - { - *existing_client_id = Some(client_id.to_string()); - return true; - } - } - false -} - struct PendingTurn { id: String, items: Vec, @@ -1311,6 +1250,7 @@ mod tests { fn builds_multiple_turns_with_reasoning_items() { let events = vec![ EventMsg::UserMessage(UserMessageEvent { + client_id: None, message: "First turn".into(), images: Some(vec!["https://example.com/one.png".into()]), text_elements: Vec::new(), @@ -1329,6 +1269,7 @@ mod tests { text: "full reasoning".into(), }), EventMsg::UserMessage(UserMessageEvent { + client_id: None, message: "Second turn".into(), images: None, text_elements: Vec::new(), @@ -1419,6 +1360,7 @@ mod tests { let local_path = PathBuf::from("/tmp/local.png"); let events = vec![RolloutItem::EventMsg(EventMsg::UserMessage( UserMessageEvent { + client_id: None, message: "inspect these".into(), images: Some(vec!["https://example.com/image.png".into()]), image_details: vec![Some(ImageDetail::Original)], @@ -1467,6 +1409,7 @@ mod tests { collaboration_mode_kind: Default::default(), }), EventMsg::UserMessage(UserMessageEvent { + client_id: None, message: "hello".into(), images: None, text_elements: Vec::new(), @@ -1513,7 +1456,7 @@ mod tests { } #[test] - fn merges_user_message_client_id_from_lifecycle_events() { + fn preserves_user_message_client_id_from_legacy_event() { let turn_id = "turn-1"; let thread_id = ThreadId::new(); let events = vec![ @@ -1524,13 +1467,6 @@ mod tests { model_context_window: None, collaboration_mode_kind: Default::default(), }), - EventMsg::UserMessage(UserMessageEvent { - message: "hello".into(), - images: None, - text_elements: Vec::new(), - local_images: Vec::new(), - ..Default::default() - }), EventMsg::ItemStarted(ItemStartedEvent { thread_id, turn_id: turn_id.to_string(), @@ -1544,6 +1480,14 @@ mod tests { }), started_at_ms: 0, }), + EventMsg::UserMessage(UserMessageEvent { + client_id: Some("client-message-1".to_string()), + message: "hello".into(), + images: None, + text_elements: Vec::new(), + local_images: Vec::new(), + ..Default::default() + }), EventMsg::TurnComplete(TurnCompleteEvent { turn_id: turn_id.to_string(), last_agent_message: None, @@ -1608,6 +1552,7 @@ mod tests { collaboration_mode_kind: Default::default(), })), RolloutItem::EventMsg(EventMsg::UserMessage(UserMessageEvent { + client_id: None, message: "generate an image".into(), images: None, text_elements: Vec::new(), @@ -1667,6 +1612,7 @@ mod tests { fn splits_reasoning_when_interleaved() { let events = vec![ EventMsg::UserMessage(UserMessageEvent { + client_id: None, message: "Turn start".into(), images: None, text_elements: Vec::new(), @@ -1720,6 +1666,7 @@ mod tests { fn marks_turn_as_interrupted_when_aborted() { let events = vec![ EventMsg::UserMessage(UserMessageEvent { + client_id: None, message: "Please do the thing".into(), images: None, text_elements: Vec::new(), @@ -1738,6 +1685,7 @@ mod tests { duration_ms: None, }), EventMsg::UserMessage(UserMessageEvent { + client_id: None, message: "Let's try again".into(), images: None, text_elements: Vec::new(), @@ -1811,6 +1759,7 @@ mod tests { fn drops_last_turns_on_thread_rollback() { let events = vec![ EventMsg::UserMessage(UserMessageEvent { + client_id: None, message: "First".into(), images: None, text_elements: Vec::new(), @@ -1823,6 +1772,7 @@ mod tests { memory_citation: None, }), EventMsg::UserMessage(UserMessageEvent { + client_id: None, message: "Second".into(), images: None, text_elements: Vec::new(), @@ -1836,6 +1786,7 @@ mod tests { }), EventMsg::ThreadRolledBack(ThreadRolledBackEvent { num_turns: 1 }), EventMsg::UserMessage(UserMessageEvent { + client_id: None, message: "Third".into(), images: None, text_elements: Vec::new(), @@ -1904,6 +1855,7 @@ mod tests { fn thread_rollback_clears_all_turns_when_num_turns_exceeds_history() { let events = vec![ EventMsg::UserMessage(UserMessageEvent { + client_id: None, message: "One".into(), images: None, text_elements: Vec::new(), @@ -1916,6 +1868,7 @@ mod tests { memory_citation: None, }), EventMsg::UserMessage(UserMessageEvent { + client_id: None, message: "Two".into(), images: None, text_elements: Vec::new(), @@ -1949,6 +1902,7 @@ mod tests { collaboration_mode_kind: Default::default(), }), EventMsg::UserMessage(UserMessageEvent { + client_id: None, message: "Start".into(), images: None, text_elements: Vec::new(), @@ -1956,6 +1910,7 @@ mod tests { ..Default::default() }), EventMsg::UserMessage(UserMessageEvent { + client_id: None, message: "Steer".into(), images: None, text_elements: Vec::new(), @@ -2012,6 +1967,7 @@ mod tests { collaboration_mode_kind: Default::default(), }), EventMsg::UserMessage(UserMessageEvent { + client_id: None, message: "run tools".into(), images: None, text_elements: Vec::new(), @@ -2191,6 +2147,7 @@ mod tests { collaboration_mode_kind: Default::default(), }), EventMsg::UserMessage(UserMessageEvent { + client_id: None, message: "run dynamic tool".into(), images: None, text_elements: Vec::new(), @@ -2258,6 +2215,7 @@ mod tests { collaboration_mode_kind: Default::default(), }), EventMsg::UserMessage(UserMessageEvent { + client_id: None, message: "run tools".into(), images: None, text_elements: Vec::new(), @@ -2349,6 +2307,7 @@ mod tests { collaboration_mode_kind: Default::default(), }), EventMsg::UserMessage(UserMessageEvent { + client_id: None, message: "review this command".into(), images: None, text_elements: Vec::new(), @@ -2434,6 +2393,7 @@ mod tests { collaboration_mode_kind: Default::default(), }), EventMsg::UserMessage(UserMessageEvent { + client_id: None, message: "run a subcommand".into(), images: None, text_elements: Vec::new(), @@ -2499,6 +2459,7 @@ mod tests { collaboration_mode_kind: Default::default(), }), EventMsg::UserMessage(UserMessageEvent { + client_id: None, message: "first".into(), images: None, text_elements: Vec::new(), @@ -2520,6 +2481,7 @@ mod tests { collaboration_mode_kind: Default::default(), }), EventMsg::UserMessage(UserMessageEvent { + client_id: None, message: "second".into(), images: None, text_elements: Vec::new(), @@ -2595,6 +2557,7 @@ mod tests { collaboration_mode_kind: Default::default(), }), EventMsg::UserMessage(UserMessageEvent { + client_id: None, message: "first".into(), images: None, text_elements: Vec::new(), @@ -2616,6 +2579,7 @@ mod tests { collaboration_mode_kind: Default::default(), }), EventMsg::UserMessage(UserMessageEvent { + client_id: None, message: "second".into(), images: None, text_elements: Vec::new(), @@ -2687,6 +2651,7 @@ mod tests { collaboration_mode_kind: Default::default(), }), EventMsg::UserMessage(UserMessageEvent { + client_id: None, message: "apply patch".into(), images: None, text_elements: Vec::new(), @@ -2754,6 +2719,7 @@ mod tests { collaboration_mode_kind: Default::default(), }), EventMsg::UserMessage(UserMessageEvent { + client_id: None, message: "apply patch".into(), images: None, text_elements: Vec::new(), @@ -2821,6 +2787,7 @@ mod tests { collaboration_mode_kind: Default::default(), }), EventMsg::UserMessage(UserMessageEvent { + client_id: None, message: "first".into(), images: None, text_elements: Vec::new(), @@ -2842,6 +2809,7 @@ mod tests { collaboration_mode_kind: Default::default(), }), EventMsg::UserMessage(UserMessageEvent { + client_id: None, message: "second".into(), images: None, text_elements: Vec::new(), @@ -2891,6 +2859,7 @@ mod tests { collaboration_mode_kind: Default::default(), }), EventMsg::UserMessage(UserMessageEvent { + client_id: None, message: "first".into(), images: None, text_elements: Vec::new(), @@ -2912,6 +2881,7 @@ mod tests { collaboration_mode_kind: Default::default(), }), EventMsg::UserMessage(UserMessageEvent { + client_id: None, message: "second".into(), images: None, text_elements: Vec::new(), @@ -2986,6 +2956,7 @@ mod tests { fn reconstructs_collab_resume_end_item() { let events = vec![ EventMsg::UserMessage(UserMessageEvent { + client_id: None, message: "resume agent".into(), images: None, text_elements: Vec::new(), @@ -3044,6 +3015,7 @@ mod tests { .expect("valid receiver thread id"); let events = vec![ EventMsg::UserMessage(UserMessageEvent { + client_id: None, message: "spawn agent".into(), images: None, text_elements: Vec::new(), @@ -3106,6 +3078,7 @@ mod tests { .expect("valid receiver thread id"); let events = vec![ EventMsg::UserMessage(UserMessageEvent { + client_id: None, message: "redirect".into(), images: None, text_elements: Vec::new(), @@ -3170,6 +3143,7 @@ mod tests { fn rollback_failed_error_does_not_mark_turn_failed() { let events = vec![ EventMsg::UserMessage(UserMessageEvent { + client_id: None, message: "hello".into(), images: None, text_elements: Vec::new(), @@ -3208,6 +3182,7 @@ mod tests { collaboration_mode_kind: Default::default(), }), EventMsg::UserMessage(UserMessageEvent { + client_id: None, message: "hello".into(), images: None, text_elements: Vec::new(), @@ -3266,6 +3241,7 @@ mod tests { collaboration_mode_kind: Default::default(), }), EventMsg::UserMessage(UserMessageEvent { + client_id: None, message: "hello".into(), images: None, text_elements: Vec::new(), @@ -3325,6 +3301,7 @@ mod tests { collaboration_mode_kind: Default::default(), })), RolloutItem::EventMsg(EventMsg::UserMessage(UserMessageEvent { + client_id: None, message: "hello".into(), images: None, text_elements: Vec::new(), diff --git a/codex-rs/app-server-protocol/src/protocol/v2/item.rs b/codex-rs/app-server-protocol/src/protocol/v2/item.rs index 1e775836a6b..d68485565ee 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2/item.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2/item.rs @@ -214,7 +214,6 @@ pub enum ThreadItem { #[ts(rename_all = "camelCase")] UserMessage { id: String, - #[serde(default)] client_id: Option, content: Vec, }, diff --git a/codex-rs/app-server-protocol/src/protocol/v2/tests.rs b/codex-rs/app-server-protocol/src/protocol/v2/tests.rs index e50018259ce..4fcb9a2c929 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2/tests.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2/tests.rs @@ -2575,62 +2575,6 @@ fn core_turn_item_into_thread_item_converts_supported_variants() { ); } -#[test] -fn user_message_client_id_uses_camel_case_wire_field() { - let item = ThreadItem::UserMessage { - id: "user-1".to_string(), - client_id: Some("client-message-1".to_string()), - content: vec![UserInput::Text { - text: "hello".to_string(), - text_elements: Vec::new(), - }], - }; - - let value = serde_json::to_value(&item).expect("serialize user message"); - assert_eq!( - value, - json!({ - "type": "userMessage", - "id": "user-1", - "clientId": "client-message-1", - "content": [{ - "type": "text", - "text": "hello", - "text_elements": [], - }], - }) - ); - - let decoded = serde_json::from_value::(value).expect("deserialize user message"); - assert_eq!(decoded, item); - - let item_without_client_id = ThreadItem::UserMessage { - id: "user-1".to_string(), - client_id: None, - content: Vec::new(), - }; - - assert_eq!( - serde_json::to_value(&item_without_client_id).expect("serialize user message"), - json!({ - "type": "userMessage", - "id": "user-1", - "clientId": null, - "content": [], - }) - ); - - assert_eq!( - serde_json::from_value::(json!({ - "type": "userMessage", - "id": "user-1", - "content": [], - })) - .expect("deserialize user message without client id"), - item_without_client_id - ); -} - #[test] fn user_input_into_core_preserves_image_detail() { assert_eq!( diff --git a/codex-rs/app-server/src/bespoke_event_handling.rs b/codex-rs/app-server/src/bespoke_event_handling.rs index 5f784ea23a2..fd220ae86db 100644 --- a/codex-rs/app-server/src/bespoke_event_handling.rs +++ b/codex-rs/app-server/src/bespoke_event_handling.rs @@ -2153,6 +2153,7 @@ mod tests { let created_at = Utc::now(); let history_items = vec![ RolloutItem::EventMsg(EventMsg::UserMessage(UserMessageEvent { + client_id: None, message: "before rollback".to_string(), images: None, local_images: Vec::new(), @@ -3228,6 +3229,7 @@ mod tests { state.track_current_turn_event( "turn-1", &EventMsg::UserMessage(codex_protocol::protocol::UserMessageEvent { + client_id: None, message: "already tracked".to_string(), images: None, local_images: Vec::new(), diff --git a/codex-rs/app-server/src/request_processors/thread_processor_tests.rs b/codex-rs/app-server/src/request_processors/thread_processor_tests.rs index ebcc3544800..b5d49cf7fb2 100644 --- a/codex-rs/app-server/src/request_processors/thread_processor_tests.rs +++ b/codex-rs/app-server/src/request_processors/thread_processor_tests.rs @@ -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(), diff --git a/codex-rs/app-server/src/request_processors/token_usage_replay.rs b/codex-rs/app-server/src/request_processors/token_usage_replay.rs index b8c65645fcf..d4d2228d4b4 100644 --- a/codex-rs/app-server/src/request_processors/token_usage_replay.rs +++ b/codex-rs/app-server/src/request_processors/token_usage_replay.rs @@ -148,6 +148,7 @@ mod tests { fn token_usage_history() -> Vec { 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(), diff --git a/codex-rs/app-server/tests/suite/v2/thread_read.rs b/codex-rs/app-server/tests/suite/v2/thread_read.rs index a53b354b6e1..ee430f1593b 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_read.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_read.rs @@ -1392,6 +1392,7 @@ async fn seed_pathless_store_thread( fn store_history_items() -> Vec { vec![RolloutItem::EventMsg(EventMsg::UserMessage( UserMessageEvent { + client_id: None, message: "history from store".to_string(), images: None, local_images: Vec::new(), diff --git a/codex-rs/core/src/personality_migration_tests.rs b/codex-rs/core/src/personality_migration_tests.rs index b84d6dd3eb4..ba9a037d7a0 100644 --- a/codex-rs/core/src/personality_migration_tests.rs +++ b/codex-rs/core/src/personality_migration_tests.rs @@ -68,6 +68,7 @@ async fn write_rollout_with_user_event(dir: &Path, thread_id: ThreadId) -> io::R let user_event = RolloutLine { timestamp: TEST_TIMESTAMP.to_string(), item: RolloutItem::EventMsg(EventMsg::UserMessage(UserMessageEvent { + client_id: None, message: "hello".to_string(), images: None, local_images: Vec::new(), diff --git a/codex-rs/core/src/session/rollout_reconstruction_tests.rs b/codex-rs/core/src/session/rollout_reconstruction_tests.rs index 11b8651ae6b..59683eff68b 100644 --- a/codex-rs/core/src/session/rollout_reconstruction_tests.rs +++ b/codex-rs/core/src/session/rollout_reconstruction_tests.rs @@ -130,6 +130,7 @@ async fn record_initial_history_resumed_hydrates_previous_turn_settings_from_lif )), RolloutItem::EventMsg(EventMsg::UserMessage( codex_protocol::protocol::UserMessageEvent { + client_id: None, message: "seed".to_string(), images: None, local_images: Vec::new(), @@ -198,6 +199,7 @@ async fn reconstruct_history_rollback_keeps_history_and_metadata_in_sync_for_com )), RolloutItem::EventMsg(EventMsg::UserMessage( codex_protocol::protocol::UserMessageEvent { + client_id: None, message: "turn 1 user".to_string(), images: None, local_images: Vec::new(), @@ -228,6 +230,7 @@ async fn reconstruct_history_rollback_keeps_history_and_metadata_in_sync_for_com )), RolloutItem::EventMsg(EventMsg::UserMessage( codex_protocol::protocol::UserMessageEvent { + client_id: None, message: "turn 2 user".to_string(), images: None, local_images: Vec::new(), @@ -300,6 +303,7 @@ async fn reconstruct_history_rollback_keeps_history_and_metadata_in_sync_for_inc )), RolloutItem::EventMsg(EventMsg::UserMessage( codex_protocol::protocol::UserMessageEvent { + client_id: None, message: "turn 1 user".to_string(), images: None, local_images: Vec::new(), @@ -330,6 +334,7 @@ async fn reconstruct_history_rollback_keeps_history_and_metadata_in_sync_for_inc )), RolloutItem::EventMsg(EventMsg::UserMessage( codex_protocol::protocol::UserMessageEvent { + client_id: None, message: "turn 2 user".to_string(), images: None, local_images: Vec::new(), @@ -394,6 +399,7 @@ async fn reconstruct_history_rollback_skips_non_user_turns_for_history_and_metad )), RolloutItem::EventMsg(EventMsg::UserMessage( codex_protocol::protocol::UserMessageEvent { + client_id: None, message: "turn 1 user".to_string(), images: None, local_images: Vec::new(), @@ -424,6 +430,7 @@ async fn reconstruct_history_rollback_skips_non_user_turns_for_history_and_metad )), RolloutItem::EventMsg(EventMsg::UserMessage( codex_protocol::protocol::UserMessageEvent { + client_id: None, message: "turn 2 user".to_string(), images: None, local_images: Vec::new(), @@ -517,6 +524,7 @@ async fn reconstruct_history_rollback_counts_inter_agent_assistant_turns() { )), RolloutItem::EventMsg(EventMsg::UserMessage( codex_protocol::protocol::UserMessageEvent { + client_id: None, message: "turn 1 user".to_string(), images: None, local_images: Vec::new(), @@ -608,6 +616,7 @@ async fn reconstruct_history_rollback_clears_history_and_metadata_when_exceeding )), RolloutItem::EventMsg(EventMsg::UserMessage( codex_protocol::protocol::UserMessageEvent { + client_id: None, message: "only user".to_string(), images: None, local_images: Vec::new(), @@ -662,6 +671,7 @@ async fn record_initial_history_resumed_rollback_skips_only_user_turns() { )), RolloutItem::EventMsg(EventMsg::UserMessage( codex_protocol::protocol::UserMessageEvent { + client_id: None, message: "seed".to_string(), images: None, local_images: Vec::new(), @@ -737,6 +747,7 @@ async fn record_initial_history_resumed_rollback_drops_incomplete_user_turn_comp )), RolloutItem::EventMsg(EventMsg::UserMessage( codex_protocol::protocol::UserMessageEvent { + client_id: None, message: "seed".to_string(), images: None, local_images: Vec::new(), @@ -765,6 +776,7 @@ async fn record_initial_history_resumed_rollback_drops_incomplete_user_turn_comp )), RolloutItem::EventMsg(EventMsg::UserMessage( codex_protocol::protocol::UserMessageEvent { + client_id: None, message: "rolled back".to_string(), images: None, local_images: Vec::new(), @@ -898,6 +910,7 @@ async fn reconstruct_history_legacy_compaction_without_replacement_history_clear )), RolloutItem::EventMsg(EventMsg::UserMessage( codex_protocol::protocol::UserMessageEvent { + client_id: None, message: "after legacy compact".to_string(), images: None, local_images: Vec::new(), @@ -963,6 +976,7 @@ async fn record_initial_history_resumed_turn_context_after_compaction_reestablis )), RolloutItem::EventMsg(EventMsg::UserMessage( codex_protocol::protocol::UserMessageEvent { + client_id: None, message: "seed".to_string(), images: None, local_images: Vec::new(), @@ -1068,6 +1082,7 @@ async fn record_initial_history_resumed_aborted_turn_without_id_clears_active_tu )), RolloutItem::EventMsg(EventMsg::UserMessage( codex_protocol::protocol::UserMessageEvent { + client_id: None, message: "seed".to_string(), images: None, local_images: Vec::new(), @@ -1096,6 +1111,7 @@ async fn record_initial_history_resumed_aborted_turn_without_id_clears_active_tu )), RolloutItem::EventMsg(EventMsg::UserMessage( codex_protocol::protocol::UserMessageEvent { + client_id: None, message: "aborted".to_string(), images: None, local_images: Vec::new(), @@ -1178,6 +1194,7 @@ async fn record_initial_history_resumed_unmatched_abort_preserves_active_turn_fo )), RolloutItem::EventMsg(EventMsg::UserMessage( codex_protocol::protocol::UserMessageEvent { + client_id: None, message: "seed".to_string(), images: None, local_images: Vec::new(), @@ -1206,6 +1223,7 @@ async fn record_initial_history_resumed_unmatched_abort_preserves_active_turn_fo )), RolloutItem::EventMsg(EventMsg::UserMessage( codex_protocol::protocol::UserMessageEvent { + client_id: None, message: "current".to_string(), images: None, local_images: Vec::new(), @@ -1297,6 +1315,7 @@ async fn record_initial_history_resumed_trailing_incomplete_turn_compaction_clea )), RolloutItem::EventMsg(EventMsg::UserMessage( codex_protocol::protocol::UserMessageEvent { + client_id: None, message: "seed".to_string(), images: None, local_images: Vec::new(), @@ -1325,6 +1344,7 @@ async fn record_initial_history_resumed_trailing_incomplete_turn_compaction_clea )), RolloutItem::EventMsg(EventMsg::UserMessage( codex_protocol::protocol::UserMessageEvent { + client_id: None, message: "incomplete".to_string(), images: None, local_images: Vec::new(), @@ -1377,6 +1397,7 @@ async fn record_initial_history_resumed_trailing_incomplete_turn_preserves_turn_ )), RolloutItem::EventMsg(EventMsg::UserMessage( codex_protocol::protocol::UserMessageEvent { + client_id: None, message: "incomplete".to_string(), images: None, local_images: Vec::new(), @@ -1452,6 +1473,7 @@ async fn record_initial_history_resumed_replaced_incomplete_compacted_turn_clear )), RolloutItem::EventMsg(EventMsg::UserMessage( codex_protocol::protocol::UserMessageEvent { + client_id: None, message: "seed".to_string(), images: None, local_images: Vec::new(), @@ -1480,6 +1502,7 @@ async fn record_initial_history_resumed_replaced_incomplete_compacted_turn_clear )), RolloutItem::EventMsg(EventMsg::UserMessage( codex_protocol::protocol::UserMessageEvent { + client_id: None, message: "compacted".to_string(), images: None, local_images: Vec::new(), diff --git a/codex-rs/core/src/session/tests.rs b/codex-rs/core/src/session/tests.rs index b80e0f64eee..f34ed7a18f9 100644 --- a/codex-rs/core/src/session/tests.rs +++ b/codex-rs/core/src/session/tests.rs @@ -2490,6 +2490,7 @@ async fn record_initial_history_forked_hydrates_previous_turn_settings() { )), RolloutItem::EventMsg(EventMsg::UserMessage( codex_protocol::protocol::UserMessageEvent { + client_id: None, message: "forked seed".to_string(), images: None, local_images: Vec::new(), @@ -2686,6 +2687,7 @@ async fn thread_rollback_recomputes_previous_turn_settings_and_reference_context )), RolloutItem::EventMsg(EventMsg::UserMessage( codex_protocol::protocol::UserMessageEvent { + client_id: None, message: "turn 1 user".to_string(), images: None, local_images: Vec::new(), @@ -2714,6 +2716,7 @@ async fn thread_rollback_recomputes_previous_turn_settings_and_reference_context )), RolloutItem::EventMsg(EventMsg::UserMessage( codex_protocol::protocol::UserMessageEvent { + client_id: None, message: "turn 2 user".to_string(), images: None, local_images: Vec::new(), @@ -2798,6 +2801,7 @@ async fn thread_rollback_restores_cleared_reference_context_item_after_compactio }, )), RolloutItem::EventMsg(EventMsg::UserMessage(UserMessageEvent { + client_id: None, message: "turn 1 user".to_string(), images: None, local_images: Vec::new(), @@ -2844,6 +2848,7 @@ async fn thread_rollback_restores_cleared_reference_context_item_after_compactio }, )), RolloutItem::EventMsg(EventMsg::UserMessage(UserMessageEvent { + client_id: None, message: "turn 2 user".to_string(), images: None, local_images: Vec::new(), @@ -2900,6 +2905,7 @@ async fn thread_rollback_persists_marker_and_replays_cumulatively() { }, )), RolloutItem::EventMsg(EventMsg::UserMessage(UserMessageEvent { + client_id: None, message: "turn 1 user".to_string(), images: None, local_images: Vec::new(), @@ -2926,6 +2932,7 @@ async fn thread_rollback_persists_marker_and_replays_cumulatively() { }, )), RolloutItem::EventMsg(EventMsg::UserMessage(UserMessageEvent { + client_id: None, message: "turn 2 user".to_string(), images: None, local_images: Vec::new(), @@ -2952,6 +2959,7 @@ async fn thread_rollback_persists_marker_and_replays_cumulatively() { }, )), RolloutItem::EventMsg(EventMsg::UserMessage(UserMessageEvent { + client_id: None, message: "turn 3 user".to_string(), images: None, local_images: Vec::new(), @@ -7786,6 +7794,7 @@ async fn record_context_updates_and_set_reference_context_item_persists_full_rei session .persist_rollout_items(&[RolloutItem::EventMsg(EventMsg::UserMessage( UserMessageEvent { + client_id: None, message: "seed rollout".to_string(), images: None, local_images: Vec::new(), @@ -8239,6 +8248,7 @@ async fn task_finish_emits_turn_item_lifecycle_for_leftover_pending_user_input() assert!(matches!( fourth.msg, EventMsg::UserMessage(UserMessageEvent { + client_id: None, message, images, text_elements, diff --git a/codex-rs/core/src/thread_manager_tests.rs b/codex-rs/core/src/thread_manager_tests.rs index c79a6859d79..76880eeb9ed 100644 --- a/codex-rs/core/src/thread_manager_tests.rs +++ b/codex-rs/core/src/thread_manager_tests.rs @@ -1124,6 +1124,7 @@ fn multi_agent_v2_interrupted_marker_uses_developer_input_message() { fn completed_legacy_event_history_is_not_mid_turn() { let completed_history = InitialHistory::Forked(vec![ RolloutItem::EventMsg(EventMsg::UserMessage(UserMessageEvent { + client_id: None, message: "hello".to_string(), images: None, text_elements: Vec::new(), @@ -1152,6 +1153,7 @@ fn mixed_response_and_legacy_user_event_history_is_mid_turn() { let mixed_history = InitialHistory::Forked(vec![ RolloutItem::ResponseItem(user_msg("hello")), RolloutItem::EventMsg(EventMsg::UserMessage(UserMessageEvent { + client_id: None, message: "hello".to_string(), images: None, text_elements: Vec::new(), diff --git a/codex-rs/core/tests/suite/personality_migration.rs b/codex-rs/core/tests/suite/personality_migration.rs index 7c9183f8d91..9cee89d761c 100644 --- a/codex-rs/core/tests/suite/personality_migration.rs +++ b/codex-rs/core/tests/suite/personality_migration.rs @@ -84,6 +84,7 @@ async fn write_rollout_with_user_event(dir: &Path, thread_id: ThreadId) -> io::R let user_event = RolloutLine { timestamp: TEST_TIMESTAMP.to_string(), item: RolloutItem::EventMsg(EventMsg::UserMessage(UserMessageEvent { + client_id: None, message: "hello".to_string(), images: None, local_images: Vec::new(), diff --git a/codex-rs/core/tests/suite/resume_warning.rs b/codex-rs/core/tests/suite/resume_warning.rs index 51242ede2af..a470e45dbe9 100644 --- a/codex-rs/core/tests/suite/resume_warning.rs +++ b/codex-rs/core/tests/suite/resume_warning.rs @@ -56,6 +56,7 @@ fn resume_history( collaboration_mode_kind: ModeKind::Default, })), RolloutItem::EventMsg(EventMsg::UserMessage(UserMessageEvent { + client_id: None, message: "seed".to_string(), images: None, local_images: vec![], diff --git a/codex-rs/core/tests/suite/sqlite_state.rs b/codex-rs/core/tests/suite/sqlite_state.rs index 8cbc46e1b59..85c1e08c842 100644 --- a/codex-rs/core/tests/suite/sqlite_state.rs +++ b/codex-rs/core/tests/suite/sqlite_state.rs @@ -236,6 +236,7 @@ async fn backfill_scans_existing_rollouts() -> Result<()> { RolloutLine { timestamp: "2026-01-27T12:00:01Z".to_string(), item: RolloutItem::EventMsg(EventMsg::UserMessage(UserMessageEvent { + client_id: None, message: "hello from backfill".to_string(), images: None, local_images: Vec::new(), diff --git a/codex-rs/external-agent-sessions/src/export.rs b/codex-rs/external-agent-sessions/src/export.rs index 01220c07c17..e09805d4d25 100644 --- a/codex-rs/external-agent-sessions/src/export.rs +++ b/codex-rs/external-agent-sessions/src/export.rs @@ -78,6 +78,7 @@ fn rollout_items_from_messages(messages: &[ConversationMessage]) -> Vec, pub message: String, /// Image URLs sourced from `UserInput::Image`. These are safe /// to replay in legacy UI history events and correspond to images sent to @@ -5091,6 +5093,7 @@ mod tests { #[test] fn user_message_event_serializes_empty_metadata_vectors() -> Result<()> { let event = UserMessageEvent { + client_id: None, message: "hello".to_string(), images: None, local_images: Vec::new(), @@ -5136,7 +5139,7 @@ mod tests { #[test] fn user_message_item_legacy_event_preserves_image_details() { let local_path = PathBuf::from("/tmp/local.png"); - let item = UserMessageItem::new(&[ + let mut item = UserMessageItem::new(&[ crate::user_input::UserInput::Image { image_url: "https://example.com/first.png".to_string(), detail: Some(ImageDetail::Original), @@ -5150,6 +5153,7 @@ mod tests { detail: Some(ImageDetail::Original), }, ]); + item.client_id = Some("client-message-1".to_string()); let EventMsg::UserMessage(event) = item.as_legacy_event() else { panic!("expected user message event"); @@ -5162,6 +5166,7 @@ mod tests { "https://example.com/second.png".to_string(), ]) ); + assert_eq!(event.client_id, Some("client-message-1".to_string())); assert_eq!(event.image_details, vec![Some(ImageDetail::Original)]); assert_eq!(event.local_images, vec![local_path]); assert_eq!(event.local_image_details, vec![Some(ImageDetail::Original)]); diff --git a/codex-rs/rollout/src/recorder_tests.rs b/codex-rs/rollout/src/recorder_tests.rs index 90fe15c3120..1c6cb5ec43c 100644 --- a/codex-rs/rollout/src/recorder_tests.rs +++ b/codex-rs/rollout/src/recorder_tests.rs @@ -109,6 +109,7 @@ async fn state_db_init_backfills_before_returning() -> anyhow::Result<()> { RolloutLine { timestamp: "2026-01-27T12:34:57Z".to_string(), item: RolloutItem::EventMsg(EventMsg::UserMessage(UserMessageEvent { + client_id: None, message: "hello from startup backfill".to_string(), images: None, local_images: Vec::new(), @@ -400,6 +401,7 @@ async fn recorder_materializes_on_flush_with_pending_items() -> std::io::Result< recorder .record_canonical_items(&[RolloutItem::EventMsg(EventMsg::UserMessage( UserMessageEvent { + client_id: None, message: "first-user-message".to_string(), images: None, local_images: Vec::new(), diff --git a/codex-rs/rollout/src/tests.rs b/codex-rs/rollout/src/tests.rs index bcd395d8204..4f28f3464e3 100644 --- a/codex-rs/rollout/src/tests.rs +++ b/codex-rs/rollout/src/tests.rs @@ -1400,6 +1400,7 @@ async fn test_updated_at_uses_file_mtime() -> Result<()> { let user_event_line = RolloutLine { timestamp: ts.to_string(), item: RolloutItem::EventMsg(EventMsg::UserMessage(UserMessageEvent { + client_id: None, message: "hello".into(), images: None, text_elements: Vec::new(), diff --git a/codex-rs/state/src/extract.rs b/codex-rs/state/src/extract.rs index 2369addf236..5cb05850cff 100644 --- a/codex-rs/state/src/extract.rs +++ b/codex-rs/state/src/extract.rs @@ -200,6 +200,7 @@ mod tests { fn event_msg_user_messages_set_title_and_first_user_message() { let mut metadata = metadata_for_test(); let item = RolloutItem::EventMsg(EventMsg::UserMessage(UserMessageEvent { + client_id: None, message: format!("{USER_MESSAGE_BEGIN} actual user request"), images: Some(vec![]), local_images: vec![], @@ -221,6 +222,7 @@ mod tests { fn event_msg_image_only_user_message_sets_image_placeholder_preview() { let mut metadata = metadata_for_test(); let item = RolloutItem::EventMsg(EventMsg::UserMessage(UserMessageEvent { + client_id: None, message: String::new(), images: Some(vec!["https://example.com/image.png".to_string()]), local_images: vec![], @@ -245,6 +247,7 @@ mod tests { fn event_msg_blank_user_message_without_images_keeps_first_user_message_empty() { let mut metadata = metadata_for_test(); let item = RolloutItem::EventMsg(EventMsg::UserMessage(UserMessageEvent { + client_id: None, message: " ".to_string(), images: Some(vec![]), local_images: vec![], @@ -285,6 +288,7 @@ mod tests { assert_eq!(metadata.title, ""); let user_item = RolloutItem::EventMsg(EventMsg::UserMessage(UserMessageEvent { + client_id: None, message: format!("{USER_MESSAGE_BEGIN} next normal prompt"), images: Some(vec![]), local_images: vec![], diff --git a/codex-rs/thread-store/src/local/mod.rs b/codex-rs/thread-store/src/local/mod.rs index a6245796a3c..7cb2ff827f6 100644 --- a/codex-rs/thread-store/src/local/mod.rs +++ b/codex-rs/thread-store/src/local/mod.rs @@ -1042,6 +1042,7 @@ mod tests { fn user_message_item(message: &str) -> RolloutItem { RolloutItem::EventMsg(EventMsg::UserMessage(UserMessageEvent { + client_id: None, message: message.to_string(), images: None, local_images: Vec::new(), diff --git a/codex-rs/thread-store/src/thread_metadata_sync.rs b/codex-rs/thread-store/src/thread_metadata_sync.rs index 408c792da8e..d25a0e65a29 100644 --- a/codex-rs/thread-store/src/thread_metadata_sync.rs +++ b/codex-rs/thread-store/src/thread_metadata_sync.rs @@ -535,6 +535,7 @@ mod tests { fn user_message(message: &str) -> UserMessageEvent { UserMessageEvent { + client_id: None, message: message.to_string(), images: None, local_images: Vec::new(),