[app-server] fix: ensure thread_id and turn_id are on all events (#7408)

This is an improvement for client-side developer ergonomics by
simplifying the state the client needs to keep track of.
This commit is contained in:
Owen Lin
2025-12-01 08:50:47 -08:00
committed by GitHub
parent a421eba31f
commit 44d92675eb
2 changed files with 31 additions and 0 deletions

View File

@@ -1232,6 +1232,7 @@ pub struct TurnCompletedNotification {
/// Notification that the turn-level unified diff has changed.
/// Contains the latest aggregated diff across all file changes in the turn.
pub struct TurnDiffUpdatedNotification {
pub thread_id: String,
pub turn_id: String,
pub diff: String,
}
@@ -1304,6 +1305,8 @@ pub struct ItemCompletedNotification {
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct AgentMessageDeltaNotification {
pub thread_id: String,
pub turn_id: String,
pub item_id: String,
pub delta: String,
}
@@ -1312,6 +1315,8 @@ pub struct AgentMessageDeltaNotification {
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ReasoningSummaryTextDeltaNotification {
pub thread_id: String,
pub turn_id: String,
pub item_id: String,
pub delta: String,
pub summary_index: i64,
@@ -1321,6 +1326,8 @@ pub struct ReasoningSummaryTextDeltaNotification {
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ReasoningSummaryPartAddedNotification {
pub thread_id: String,
pub turn_id: String,
pub item_id: String,
pub summary_index: i64,
}
@@ -1329,6 +1336,8 @@ pub struct ReasoningSummaryPartAddedNotification {
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ReasoningTextDeltaNotification {
pub thread_id: String,
pub turn_id: String,
pub item_id: String,
pub delta: String,
pub content_index: i64,
@@ -1338,6 +1347,8 @@ pub struct ReasoningTextDeltaNotification {
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct CommandExecutionOutputDeltaNotification {
pub thread_id: String,
pub turn_id: String,
pub item_id: String,
pub delta: String,
}
@@ -1346,6 +1357,8 @@ pub struct CommandExecutionOutputDeltaNotification {
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct McpToolCallProgressNotification {
pub thread_id: String,
pub turn_id: String,
pub item_id: String,
pub message: String,
}

View File

@@ -260,6 +260,8 @@ pub(crate) async fn apply_bespoke_event_handling(
}
EventMsg::AgentMessageContentDelta(event) => {
let notification = AgentMessageDeltaNotification {
thread_id: conversation_id.to_string(),
turn_id: event_turn_id.clone(),
item_id: event.item_id,
delta: event.delta,
};
@@ -278,6 +280,8 @@ pub(crate) async fn apply_bespoke_event_handling(
}
EventMsg::ReasoningContentDelta(event) => {
let notification = ReasoningSummaryTextDeltaNotification {
thread_id: conversation_id.to_string(),
turn_id: event_turn_id.clone(),
item_id: event.item_id,
delta: event.delta,
summary_index: event.summary_index,
@@ -290,6 +294,8 @@ pub(crate) async fn apply_bespoke_event_handling(
}
EventMsg::ReasoningRawContentDelta(event) => {
let notification = ReasoningTextDeltaNotification {
thread_id: conversation_id.to_string(),
turn_id: event_turn_id.clone(),
item_id: event.item_id,
delta: event.delta,
content_index: event.content_index,
@@ -300,6 +306,8 @@ pub(crate) async fn apply_bespoke_event_handling(
}
EventMsg::AgentReasoningSectionBreak(event) => {
let notification = ReasoningSummaryPartAddedNotification {
thread_id: conversation_id.to_string(),
turn_id: event_turn_id.clone(),
item_id: event.item_id,
summary_index: event.summary_index,
};
@@ -494,6 +502,8 @@ pub(crate) async fn apply_bespoke_event_handling(
}
EventMsg::ExecCommandOutputDelta(exec_command_output_delta_event) => {
let notification = CommandExecutionOutputDeltaNotification {
thread_id: conversation_id.to_string(),
turn_id: event_turn_id.clone(),
item_id: exec_command_output_delta_event.call_id.clone(),
delta: String::from_utf8_lossy(&exec_command_output_delta_event.chunk).to_string(),
};
@@ -588,6 +598,7 @@ pub(crate) async fn apply_bespoke_event_handling(
}
EventMsg::TurnDiff(turn_diff_event) => {
handle_turn_diff(
conversation_id,
&event_turn_id,
turn_diff_event,
api_version,
@@ -610,6 +621,7 @@ pub(crate) async fn apply_bespoke_event_handling(
}
async fn handle_turn_diff(
conversation_id: ConversationId,
event_turn_id: &str,
turn_diff_event: TurnDiffEvent,
api_version: ApiVersion,
@@ -617,6 +629,7 @@ async fn handle_turn_diff(
) {
if let ApiVersion::V2 = api_version {
let notification = TurnDiffUpdatedNotification {
thread_id: conversation_id.to_string(),
turn_id: event_turn_id.to_string(),
diff: turn_diff_event.unified_diff,
};
@@ -1774,8 +1787,10 @@ mod tests {
let (tx, mut rx) = mpsc::channel(CHANNEL_CAPACITY);
let outgoing = OutgoingMessageSender::new(tx);
let unified_diff = "--- a\n+++ b\n".to_string();
let conversation_id = ConversationId::new();
handle_turn_diff(
conversation_id,
"turn-1",
TurnDiffEvent {
unified_diff: unified_diff.clone(),
@@ -1793,6 +1808,7 @@ mod tests {
OutgoingMessage::AppServerNotification(ServerNotification::TurnDiffUpdated(
notification,
)) => {
assert_eq!(notification.thread_id, conversation_id.to_string());
assert_eq!(notification.turn_id, "turn-1");
assert_eq!(notification.diff, unified_diff);
}
@@ -1806,8 +1822,10 @@ mod tests {
async fn test_handle_turn_diff_is_noop_for_v1() -> Result<()> {
let (tx, mut rx) = mpsc::channel(CHANNEL_CAPACITY);
let outgoing = OutgoingMessageSender::new(tx);
let conversation_id = ConversationId::new();
handle_turn_diff(
conversation_id,
"turn-1",
TurnDiffEvent {
unified_diff: "diff".to_string(),