Skip to content

Commit aaec8ab

Browse files
authored
feat: detached review (#7292)
1 parent cbd7d0d commit aaec8ab

File tree

15 files changed

+527
-226
lines changed

15 files changed

+527
-226
lines changed

codex-rs/app-server-protocol/src/protocol/common.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ client_request_definitions! {
131131
},
132132
ReviewStart => "review/start" {
133133
params: v2::ReviewStartParams,
134-
response: v2::TurnStartResponse,
134+
response: v2::ReviewStartResponse,
135135
},
136136

137137
ModelList => "model/list" {

codex-rs/app-server-protocol/src/protocol/v2.rs

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,12 @@ v2_enum_from_core!(
130130
}
131131
);
132132

133+
v2_enum_from_core!(
134+
pub enum ReviewDelivery from codex_protocol::protocol::ReviewDelivery {
135+
Inline, Detached
136+
}
137+
);
138+
133139
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
134140
#[serde(rename_all = "camelCase")]
135141
#[ts(export_to = "v2/")]
@@ -908,9 +914,22 @@ pub struct ReviewStartParams {
908914
pub thread_id: String,
909915
pub target: ReviewTarget,
910916

911-
/// When true, also append the final review message to the original thread.
917+
/// Where to run the review: inline (default) on the current thread or
918+
/// detached on a new thread (returned in `reviewThreadId`).
912919
#[serde(default)]
913-
pub append_to_original_thread: bool,
920+
pub delivery: Option<ReviewDelivery>,
921+
}
922+
923+
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
924+
#[serde(rename_all = "camelCase")]
925+
#[ts(export_to = "v2/")]
926+
pub struct ReviewStartResponse {
927+
pub turn: Turn,
928+
/// Identifies the thread where the review runs.
929+
///
930+
/// For inline reviews, this is the original thread id.
931+
/// For detached reviews, this is the id of the new review thread.
932+
pub review_thread_id: String,
914933
}
915934

916935
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
@@ -1063,7 +1082,10 @@ pub enum ThreadItem {
10631082
ImageView { id: String, path: String },
10641083
#[serde(rename_all = "camelCase")]
10651084
#[ts(rename_all = "camelCase")]
1066-
CodeReview { id: String, review: String },
1085+
EnteredReviewMode { id: String, review: String },
1086+
#[serde(rename_all = "camelCase")]
1087+
#[ts(rename_all = "camelCase")]
1088+
ExitedReviewMode { id: String, review: String },
10671089
}
10681090

10691091
impl From<CoreTurnItem> for ThreadItem {

codex-rs/app-server/README.md

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ The JSON-RPC API exposes dedicated methods for managing Codex conversations. Thr
6565
- `thread/archive` — move a thread’s rollout file into the archived directory; returns `{}` on success.
6666
- `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.
6767
- `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"`.
68-
- `review/start` — kick off Codex’s automated reviewer for a thread; responds like `turn/start` and emits a `item/completed` notification with a `codeReview` item when results are ready.
68+
- `review/start` — kick off Codex’s automated reviewer for a thread; responds like `turn/start` and emits `item/started`/`item/completed` notifications with `enteredReviewMode` and `exitedReviewMode` items, plus a final assistant `agentMessage` containing the review.
6969

7070
### 1) Start or resume a thread
7171

@@ -190,49 +190,56 @@ Use `review/start` to run Codex’s reviewer on the currently checked-out projec
190190
- `{"type":"baseBranch","branch":"main"}` — diff against the provided branch’s upstream (see prompt for the exact `git merge-base`/`git diff` instructions Codex will run).
191191
- `{"type":"commit","sha":"abc1234","title":"Optional subject"}` — review a specific commit.
192192
- `{"type":"custom","instructions":"Free-form reviewer instructions"}` — fallback prompt equivalent to the legacy manual review request.
193-
- `appendToOriginalThread` (bool, default `false`) — when `true`, Codex also records a final assistant-style message with the review summary in the original thread. When `false`, only the `codeReview` item is emitted for the review run and no extra message is added to the original thread.
193+
- `delivery` (`"inline"` or `"detached"`, default `"inline"`) — where the review runs:
194+
- `"inline"`: run the review as a new turn on the existing thread. The response’s `reviewThreadId` equals the original `threadId`, and no new `thread/started` notification is emitted.
195+
- `"detached"`: fork a new review thread from the parent conversation and run the review there. The response’s `reviewThreadId` is the id of this new review thread, and the server emits a `thread/started` notification for it before streaming review items.
194196

195197
Example request/response:
196198

197199
```json
198200
{ "method": "review/start", "id": 40, "params": {
199201
"threadId": "thr_123",
200-
"appendToOriginalThread": true,
202+
"delivery": "inline",
201203
"target": { "type": "commit", "sha": "1234567deadbeef", "title": "Polish tui colors" }
202204
} }
203-
{ "id": 40, "result": { "turn": {
204-
"id": "turn_900",
205-
"status": "inProgress",
206-
"items": [
207-
{ "type": "userMessage", "id": "turn_900", "content": [ { "type": "text", "text": "Review commit 1234567: Polish tui colors" } ] }
208-
],
209-
"error": null
210-
} } }
205+
{ "id": 40, "result": {
206+
"turn": {
207+
"id": "turn_900",
208+
"status": "inProgress",
209+
"items": [
210+
{ "type": "userMessage", "id": "turn_900", "content": [ { "type": "text", "text": "Review commit 1234567: Polish tui colors" } ] }
211+
],
212+
"error": null
213+
},
214+
"reviewThreadId": "thr_123"
215+
} }
211216
```
212217

218+
For a detached review, use `"delivery": "detached"`. The response is the same shape, but `reviewThreadId` will be the id of the new review thread (different from the original `threadId`). The server also emits a `thread/started` notification for that new thread before streaming the review turn.
219+
213220
Codex streams the usual `turn/started` notification followed by an `item/started`
214-
with the same `codeReview` item id so clients can show progress:
221+
with an `enteredReviewMode` item so clients can show progress:
215222

216223
```json
217224
{ "method": "item/started", "params": { "item": {
218-
"type": "codeReview",
225+
"type": "enteredReviewMode",
219226
"id": "turn_900",
220227
"review": "current changes"
221228
} } }
222229
```
223230

224-
When the reviewer finishes, the server emits `item/completed` containing the same
225-
`codeReview` item with the final review text:
231+
When the reviewer finishes, the server emits `item/started` and `item/completed`
232+
containing an `exitedReviewMode` item with the final review text:
226233

227234
```json
228235
{ "method": "item/completed", "params": { "item": {
229-
"type": "codeReview",
236+
"type": "exitedReviewMode",
230237
"id": "turn_900",
231238
"review": "Looks solid overall...\n\n- Prefer Stylize helpers — app.rs:10-20\n ..."
232239
} } }
233240
```
234241

235-
The `review` string is plain text that already bundles the overall explanation plus a bullet list for each structured finding (matching `ThreadItem::CodeReview` in the generated schema). Use this notification to render the reviewer output in your client.
242+
The `review` string is plain text that already bundles the overall explanation plus a bullet list for each structured finding (matching `ThreadItem::ExitedReviewMode` in the generated schema). Use this notification to render the reviewer output in your client.
236243

237244
## Events (work-in-progress)
238245

codex-rs/app-server/src/bespoke_event_handling.rs

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -340,16 +340,26 @@ pub(crate) async fn apply_bespoke_event_handling(
340340
.await;
341341
}
342342
EventMsg::EnteredReviewMode(review_request) => {
343-
let notification = ItemStartedNotification {
343+
let review = review_request.user_facing_hint;
344+
let item = ThreadItem::EnteredReviewMode {
345+
id: event_turn_id.clone(),
346+
review,
347+
};
348+
let started = ItemStartedNotification {
344349
thread_id: conversation_id.to_string(),
345350
turn_id: event_turn_id.clone(),
346-
item: ThreadItem::CodeReview {
347-
id: event_turn_id.clone(),
348-
review: review_request.user_facing_hint,
349-
},
351+
item: item.clone(),
350352
};
351353
outgoing
352-
.send_server_notification(ServerNotification::ItemStarted(notification))
354+
.send_server_notification(ServerNotification::ItemStarted(started))
355+
.await;
356+
let completed = ItemCompletedNotification {
357+
thread_id: conversation_id.to_string(),
358+
turn_id: event_turn_id.clone(),
359+
item,
360+
};
361+
outgoing
362+
.send_server_notification(ServerNotification::ItemCompleted(completed))
353363
.await;
354364
}
355365
EventMsg::ItemStarted(item_started_event) => {
@@ -375,21 +385,29 @@ pub(crate) async fn apply_bespoke_event_handling(
375385
.await;
376386
}
377387
EventMsg::ExitedReviewMode(review_event) => {
378-
let review_text = match review_event.review_output {
388+
let review = match review_event.review_output {
379389
Some(output) => render_review_output_text(&output),
380390
None => REVIEW_FALLBACK_MESSAGE.to_string(),
381391
};
382-
let review_item_id = event_turn_id.clone();
383-
let notification = ItemCompletedNotification {
392+
let item = ThreadItem::ExitedReviewMode {
393+
id: event_turn_id.clone(),
394+
review,
395+
};
396+
let started = ItemStartedNotification {
384397
thread_id: conversation_id.to_string(),
385398
turn_id: event_turn_id.clone(),
386-
item: ThreadItem::CodeReview {
387-
id: review_item_id,
388-
review: review_text,
389-
},
399+
item: item.clone(),
390400
};
391401
outgoing
392-
.send_server_notification(ServerNotification::ItemCompleted(notification))
402+
.send_server_notification(ServerNotification::ItemStarted(started))
403+
.await;
404+
let completed = ItemCompletedNotification {
405+
thread_id: conversation_id.to_string(),
406+
turn_id: event_turn_id.clone(),
407+
item,
408+
};
409+
outgoing
410+
.send_server_notification(ServerNotification::ItemCompleted(completed))
393411
.await;
394412
}
395413
EventMsg::PatchApplyBegin(patch_begin_event) => {

0 commit comments

Comments
 (0)