Skip to content

Commit b8ec97c

Browse files
authored
[App-server] add new v2 events:item/reasoning/delta, item/agentMessage/delta & item/reasoning/summaryPartAdded (#6559)
core event to app server event mapping: 1. `codex/event/reasoning_content_delta` -> `item/reasoning/summaryTextDelta`. 2. `codex/event/reasoning_raw_content_delta` -> `item/reasoning/textDelta` 3. `codex/event/agent_message_content_delta` → `item/agentMessage/delta`. 4. `codex/event/agent_reasoning_section_break` -> `item/reasoning/summaryPartAdded`. Also added a change in core to pass down content index, summary index and item id from events. Tested with the `git checkout owen/app_server_test_client && cargo run -p codex-app-server-test-client -- send-message-v2 "hello"` and verified that new events are emitted correctly.
1 parent 2c1b693 commit b8ec97c

File tree

10 files changed

+203
-43
lines changed

10 files changed

+203
-43
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,9 @@ server_notification_definitions! {
508508
McpToolCallProgress => "item/mcpToolCall/progress" (v2::McpToolCallProgressNotification),
509509
AccountUpdated => "account/updated" (v2::AccountUpdatedNotification),
510510
AccountRateLimitsUpdated => "account/rateLimits/updated" (v2::AccountRateLimitsUpdatedNotification),
511+
ReasoningSummaryTextDelta => "item/reasoning/summaryTextDelta" (v2::ReasoningSummaryTextDeltaNotification),
512+
ReasoningSummaryPartAdded => "item/reasoning/summaryPartAdded" (v2::ReasoningSummaryPartAddedNotification),
513+
ReasoningTextDelta => "item/reasoning/textDelta" (v2::ReasoningTextDeltaNotification),
511514

512515
#[serde(rename = "account/login/completed")]
513516
#[ts(rename = "account/login/completed")]

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

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -516,7 +516,10 @@ pub enum ThreadItem {
516516
},
517517
Reasoning {
518518
id: String,
519-
text: String,
519+
#[serde(default)]
520+
summary: Vec<String>,
521+
#[serde(default)]
522+
content: Vec<String>,
520523
},
521524
CommandExecution {
522525
id: String,
@@ -575,17 +578,11 @@ impl From<CoreTurnItem> for ThreadItem {
575578
.collect::<String>();
576579
ThreadItem::AgentMessage { id: agent.id, text }
577580
}
578-
CoreTurnItem::Reasoning(reasoning) => {
579-
let text = if !reasoning.summary_text.is_empty() {
580-
reasoning.summary_text.join("\n")
581-
} else {
582-
reasoning.raw_content.join("\n")
583-
};
584-
ThreadItem::Reasoning {
585-
id: reasoning.id,
586-
text,
587-
}
588-
}
581+
CoreTurnItem::Reasoning(reasoning) => ThreadItem::Reasoning {
582+
id: reasoning.id,
583+
summary: reasoning.summary_text,
584+
content: reasoning.raw_content,
585+
},
589586
CoreTurnItem::WebSearch(search) => ThreadItem::WebSearch {
590587
id: search.id,
591588
query: search.query,
@@ -719,6 +716,32 @@ pub struct AgentMessageDeltaNotification {
719716
pub delta: String,
720717
}
721718

719+
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
720+
#[serde(rename_all = "camelCase")]
721+
#[ts(export_to = "v2/")]
722+
pub struct ReasoningSummaryTextDeltaNotification {
723+
pub item_id: String,
724+
pub delta: String,
725+
pub summary_index: i64,
726+
}
727+
728+
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
729+
#[serde(rename_all = "camelCase")]
730+
#[ts(export_to = "v2/")]
731+
pub struct ReasoningSummaryPartAddedNotification {
732+
pub item_id: String,
733+
pub summary_index: i64,
734+
}
735+
736+
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
737+
#[serde(rename_all = "camelCase")]
738+
#[ts(export_to = "v2/")]
739+
pub struct ReasoningTextDeltaNotification {
740+
pub item_id: String,
741+
pub delta: String,
742+
pub content_index: i64,
743+
}
744+
722745
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
723746
#[serde(rename_all = "camelCase")]
724747
#[ts(export_to = "v2/")]
@@ -867,7 +890,8 @@ mod tests {
867890
ThreadItem::from(reasoning_item),
868891
ThreadItem::Reasoning {
869892
id: "reasoning-1".to_string(),
870-
text: "line one\nline two".to_string(),
893+
summary: vec!["line one".to_string(), "line two".to_string()],
894+
content: vec![],
871895
}
872896
);
873897

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

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use codex_app_server_protocol::AccountRateLimitsUpdatedNotification;
1212
use codex_app_server_protocol::AccountUpdatedNotification;
1313
use codex_app_server_protocol::AddConversationListenerParams;
1414
use codex_app_server_protocol::AddConversationSubscriptionResponse;
15+
use codex_app_server_protocol::AgentMessageDeltaNotification;
1516
use codex_app_server_protocol::ApplyPatchApprovalParams;
1617
use codex_app_server_protocol::ApplyPatchApprovalResponse;
1718
use codex_app_server_protocol::ArchiveConversationParams;
@@ -62,6 +63,9 @@ use codex_app_server_protocol::ModelListParams;
6263
use codex_app_server_protocol::ModelListResponse;
6364
use codex_app_server_protocol::NewConversationParams;
6465
use codex_app_server_protocol::NewConversationResponse;
66+
use codex_app_server_protocol::ReasoningSummaryPartAddedNotification;
67+
use codex_app_server_protocol::ReasoningSummaryTextDeltaNotification;
68+
use codex_app_server_protocol::ReasoningTextDeltaNotification;
6569
use codex_app_server_protocol::RemoveConversationListenerParams;
6670
use codex_app_server_protocol::RemoveConversationSubscriptionResponse;
6771
use codex_app_server_protocol::RequestId;
@@ -2681,6 +2685,48 @@ async fn apply_bespoke_event_handling(
26812685
on_patch_approval_response(event_id, rx, conversation).await;
26822686
});
26832687
}
2688+
EventMsg::AgentMessageContentDelta(event) => {
2689+
let notification = AgentMessageDeltaNotification {
2690+
item_id: event.item_id,
2691+
delta: event.delta,
2692+
};
2693+
outgoing
2694+
.send_server_notification(ServerNotification::AgentMessageDelta(notification))
2695+
.await;
2696+
}
2697+
EventMsg::ReasoningContentDelta(event) => {
2698+
let notification = ReasoningSummaryTextDeltaNotification {
2699+
item_id: event.item_id,
2700+
delta: event.delta,
2701+
summary_index: event.summary_index,
2702+
};
2703+
outgoing
2704+
.send_server_notification(ServerNotification::ReasoningSummaryTextDelta(
2705+
notification,
2706+
))
2707+
.await;
2708+
}
2709+
EventMsg::ReasoningRawContentDelta(event) => {
2710+
let notification = ReasoningTextDeltaNotification {
2711+
item_id: event.item_id,
2712+
delta: event.delta,
2713+
content_index: event.content_index,
2714+
};
2715+
outgoing
2716+
.send_server_notification(ServerNotification::ReasoningTextDelta(notification))
2717+
.await;
2718+
}
2719+
EventMsg::AgentReasoningSectionBreak(event) => {
2720+
let notification = ReasoningSummaryPartAddedNotification {
2721+
item_id: event.item_id,
2722+
summary_index: event.summary_index,
2723+
};
2724+
outgoing
2725+
.send_server_notification(ServerNotification::ReasoningSummaryPartAdded(
2726+
notification,
2727+
))
2728+
.await;
2729+
}
26842730
EventMsg::ExecApprovalRequest(ExecApprovalRequestEvent {
26852731
call_id,
26862732
command,

codex-rs/core/src/chat_completions.rs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -477,10 +477,14 @@ async fn append_reasoning_text(
477477
..
478478
}) = reasoning_item
479479
{
480+
let content_index = content.len() as i64;
480481
content.push(ReasoningItemContent::ReasoningText { text: text.clone() });
481482

482483
let _ = tx_event
483-
.send(Ok(ResponseEvent::ReasoningContentDelta(text.clone())))
484+
.send(Ok(ResponseEvent::ReasoningContentDelta {
485+
delta: text.clone(),
486+
content_index,
487+
}))
484488
.await;
485489
}
486490
}
@@ -898,20 +902,26 @@ where
898902
continue;
899903
}
900904
}
901-
Poll::Ready(Some(Ok(ResponseEvent::ReasoningContentDelta(delta)))) => {
905+
Poll::Ready(Some(Ok(ResponseEvent::ReasoningContentDelta {
906+
delta,
907+
content_index,
908+
}))) => {
902909
// Always accumulate reasoning deltas so we can emit a final Reasoning item at Completed.
903910
this.cumulative_reasoning.push_str(&delta);
904911
if matches!(this.mode, AggregateMode::Streaming) {
905912
// In streaming mode, also forward the delta immediately.
906-
return Poll::Ready(Some(Ok(ResponseEvent::ReasoningContentDelta(delta))));
913+
return Poll::Ready(Some(Ok(ResponseEvent::ReasoningContentDelta {
914+
delta,
915+
content_index,
916+
})));
907917
} else {
908918
continue;
909919
}
910920
}
911-
Poll::Ready(Some(Ok(ResponseEvent::ReasoningSummaryDelta(_)))) => {
921+
Poll::Ready(Some(Ok(ResponseEvent::ReasoningSummaryDelta { .. }))) => {
912922
continue;
913923
}
914-
Poll::Ready(Some(Ok(ResponseEvent::ReasoningSummaryPartAdded))) => {
924+
Poll::Ready(Some(Ok(ResponseEvent::ReasoningSummaryPartAdded { .. }))) => {
915925
continue;
916926
}
917927
Poll::Ready(Some(Ok(ResponseEvent::OutputItemAdded(item)))) => {

codex-rs/core/src/client.rs

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -560,6 +560,8 @@ struct SseEvent {
560560
response: Option<Value>,
561561
item: Option<Value>,
562562
delta: Option<String>,
563+
summary_index: Option<i64>,
564+
content_index: Option<i64>,
563565
}
564566

565567
#[derive(Debug, Deserialize)]
@@ -819,16 +821,22 @@ async fn process_sse<S>(
819821
}
820822
}
821823
"response.reasoning_summary_text.delta" => {
822-
if let Some(delta) = event.delta {
823-
let event = ResponseEvent::ReasoningSummaryDelta(delta);
824+
if let (Some(delta), Some(summary_index)) = (event.delta, event.summary_index) {
825+
let event = ResponseEvent::ReasoningSummaryDelta {
826+
delta,
827+
summary_index,
828+
};
824829
if tx_event.send(Ok(event)).await.is_err() {
825830
return;
826831
}
827832
}
828833
}
829834
"response.reasoning_text.delta" => {
830-
if let Some(delta) = event.delta {
831-
let event = ResponseEvent::ReasoningContentDelta(delta);
835+
if let (Some(delta), Some(content_index)) = (event.delta, event.content_index) {
836+
let event = ResponseEvent::ReasoningContentDelta {
837+
delta,
838+
content_index,
839+
};
832840
if tx_event.send(Ok(event)).await.is_err() {
833841
return;
834842
}
@@ -905,10 +913,12 @@ async fn process_sse<S>(
905913
}
906914
}
907915
"response.reasoning_summary_part.added" => {
908-
// Boundary between reasoning summary sections (e.g., titles).
909-
let event = ResponseEvent::ReasoningSummaryPartAdded;
910-
if tx_event.send(Ok(event)).await.is_err() {
911-
return;
916+
if let Some(summary_index) = event.summary_index {
917+
// Boundary between reasoning summary sections (e.g., titles).
918+
let event = ResponseEvent::ReasoningSummaryPartAdded { summary_index };
919+
if tx_event.send(Ok(event)).await.is_err() {
920+
return;
921+
}
912922
}
913923
}
914924
"response.reasoning_summary_text.done" => {}

codex-rs/core/src/client_common.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -203,9 +203,17 @@ pub enum ResponseEvent {
203203
token_usage: Option<TokenUsage>,
204204
},
205205
OutputTextDelta(String),
206-
ReasoningSummaryDelta(String),
207-
ReasoningContentDelta(String),
208-
ReasoningSummaryPartAdded,
206+
ReasoningSummaryDelta {
207+
delta: String,
208+
summary_index: i64,
209+
},
210+
ReasoningContentDelta {
211+
delta: String,
212+
content_index: i64,
213+
},
214+
ReasoningSummaryPartAdded {
215+
summary_index: i64,
216+
},
209217
RateLimits(RateLimitSnapshot),
210218
}
211219

codex-rs/core/src/codex.rs

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2232,32 +2232,47 @@ async fn try_run_turn(
22322232
error_or_panic("ReasoningSummaryDelta without active item".to_string());
22332233
}
22342234
}
2235-
ResponseEvent::ReasoningSummaryDelta(delta) => {
2235+
ResponseEvent::ReasoningSummaryDelta {
2236+
delta,
2237+
summary_index,
2238+
} => {
22362239
if let Some(active) = active_item.as_ref() {
22372240
let event = ReasoningContentDeltaEvent {
22382241
thread_id: sess.conversation_id.to_string(),
22392242
turn_id: turn_context.sub_id.clone(),
22402243
item_id: active.id(),
2241-
delta: delta.clone(),
2244+
delta,
2245+
summary_index,
22422246
};
22432247
sess.send_event(&turn_context, EventMsg::ReasoningContentDelta(event))
22442248
.await;
22452249
} else {
22462250
error_or_panic("ReasoningSummaryDelta without active item".to_string());
22472251
}
22482252
}
2249-
ResponseEvent::ReasoningSummaryPartAdded => {
2250-
let event =
2251-
EventMsg::AgentReasoningSectionBreak(AgentReasoningSectionBreakEvent {});
2252-
sess.send_event(&turn_context, event).await;
2253+
ResponseEvent::ReasoningSummaryPartAdded { summary_index } => {
2254+
if let Some(active) = active_item.as_ref() {
2255+
let event =
2256+
EventMsg::AgentReasoningSectionBreak(AgentReasoningSectionBreakEvent {
2257+
item_id: active.id(),
2258+
summary_index,
2259+
});
2260+
sess.send_event(&turn_context, event).await;
2261+
} else {
2262+
error_or_panic("ReasoningSummaryPartAdded without active item".to_string());
2263+
}
22532264
}
2254-
ResponseEvent::ReasoningContentDelta(delta) => {
2265+
ResponseEvent::ReasoningContentDelta {
2266+
delta,
2267+
content_index,
2268+
} => {
22552269
if let Some(active) = active_item.as_ref() {
22562270
let event = ReasoningRawContentDeltaEvent {
22572271
thread_id: sess.conversation_id.to_string(),
22582272
turn_id: turn_context.sub_id.clone(),
22592273
item_id: active.id(),
2260-
delta: delta.clone(),
2274+
delta,
2275+
content_index,
22612276
};
22622277
sess.send_event(&turn_context, EventMsg::ReasoningRawContentDelta(event))
22632278
.await;

0 commit comments

Comments
 (0)