Include elapsedMs in app-server protocol

This commit is contained in:
Ahmed Ibrahim
2026-01-22 18:06:59 -08:00
parent a977fb9f19
commit aeb9cc1a5b
2 changed files with 85 additions and 11 deletions

View File

@@ -70,7 +70,11 @@ impl ThreadHistoryBuilder {
let mut turn = self.new_turn();
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,
content,
elapsed_ms: None,
});
self.current_turn = Some(turn);
}
@@ -80,9 +84,11 @@ impl ThreadHistoryBuilder {
}
let id = self.next_item_id();
self.ensure_turn()
.items
.push(ThreadItem::AgentMessage { id, text });
self.ensure_turn().items.push(ThreadItem::AgentMessage {
id,
text,
elapsed_ms: None,
});
}
fn handle_agent_reasoning(&mut self, payload: &AgentReasoningEvent) {
@@ -102,6 +108,7 @@ impl ThreadHistoryBuilder {
id,
summary: vec![payload.text.clone()],
content: Vec::new(),
elapsed_ms: None,
});
}
@@ -122,6 +129,7 @@ impl ThreadHistoryBuilder {
id,
summary: Vec::new(),
content: vec![payload.text.clone()],
elapsed_ms: None,
});
}
@@ -296,6 +304,7 @@ mod tests {
url: "https://example.com/one.png".into(),
}
],
elapsed_ms: None,
}
);
assert_eq!(
@@ -303,6 +312,7 @@ mod tests {
ThreadItem::AgentMessage {
id: "item-2".into(),
text: "Hi there".into(),
elapsed_ms: None,
}
);
assert_eq!(
@@ -311,6 +321,7 @@ mod tests {
id: "item-3".into(),
summary: vec!["thinking".into()],
content: vec!["full reasoning".into()],
elapsed_ms: None,
}
);
@@ -325,6 +336,7 @@ mod tests {
text: "Second turn".into(),
text_elements: Vec::new(),
}],
elapsed_ms: None,
}
);
assert_eq!(
@@ -332,6 +344,7 @@ mod tests {
ThreadItem::AgentMessage {
id: "item-5".into(),
text: "Reply two".into(),
elapsed_ms: None,
}
);
}
@@ -370,6 +383,7 @@ mod tests {
id: "item-2".into(),
summary: vec!["first summary".into()],
content: vec!["first content".into()],
elapsed_ms: None,
}
);
assert_eq!(
@@ -378,6 +392,7 @@ mod tests {
id: "item-4".into(),
summary: vec!["second summary".into()],
content: Vec::new(),
elapsed_ms: None,
}
);
}
@@ -422,6 +437,7 @@ mod tests {
text: "Please do the thing".into(),
text_elements: Vec::new(),
}],
elapsed_ms: None,
}
);
assert_eq!(
@@ -429,6 +445,7 @@ mod tests {
ThreadItem::AgentMessage {
id: "item-2".into(),
text: "Working...".into(),
elapsed_ms: None,
}
);
@@ -443,6 +460,7 @@ mod tests {
text: "Let's try again".into(),
text_elements: Vec::new(),
}],
elapsed_ms: None,
}
);
assert_eq!(
@@ -450,6 +468,7 @@ mod tests {
ThreadItem::AgentMessage {
id: "item-4".into(),
text: "Second attempt complete.".into(),
elapsed_ms: None,
}
);
}
@@ -500,10 +519,12 @@ mod tests {
text: "First".into(),
text_elements: Vec::new(),
}],
elapsed_ms: None,
},
ThreadItem::AgentMessage {
id: "item-2".into(),
text: "A1".into(),
elapsed_ms: None,
},
],
},
@@ -518,10 +539,12 @@ mod tests {
text: "Third".into(),
text_elements: Vec::new(),
}],
elapsed_ms: None,
},
ThreadItem::AgentMessage {
id: "item-4".into(),
text: "A3".into(),
elapsed_ms: None,
},
],
},

View File

@@ -1789,10 +1789,20 @@ impl From<CoreUserInput> for UserInput {
pub enum ThreadItem {
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
UserMessage { id: String, content: Vec<UserInput> },
UserMessage {
id: String,
content: Vec<UserInput>,
#[ts(type = "number | null")]
elapsed_ms: Option<i64>,
},
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
AgentMessage { id: String, text: String },
AgentMessage {
id: String,
text: String,
#[ts(type = "number | null")]
elapsed_ms: Option<i64>,
},
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
Reasoning {
@@ -1801,6 +1811,8 @@ pub enum ThreadItem {
summary: Vec<String>,
#[serde(default)]
content: Vec<String>,
#[ts(type = "number | null")]
elapsed_ms: Option<i64>,
},
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
@@ -1824,6 +1836,8 @@ pub enum ThreadItem {
/// The duration of the command execution in milliseconds.
#[ts(type = "number | null")]
duration_ms: Option<i64>,
#[ts(type = "number | null")]
elapsed_ms: Option<i64>,
},
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
@@ -1831,6 +1845,8 @@ pub enum ThreadItem {
id: String,
changes: Vec<FileUpdateChange>,
status: PatchApplyStatus,
#[ts(type = "number | null")]
elapsed_ms: Option<i64>,
},
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
@@ -1845,6 +1861,8 @@ pub enum ThreadItem {
/// The duration of the MCP tool call in milliseconds.
#[ts(type = "number | null")]
duration_ms: Option<i64>,
#[ts(type = "number | null")]
elapsed_ms: Option<i64>,
},
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
@@ -1864,19 +1882,41 @@ pub enum ThreadItem {
prompt: Option<String>,
/// Last known status of the target agents, when available.
agents_states: HashMap<String, CollabAgentState>,
#[ts(type = "number | null")]
elapsed_ms: Option<i64>,
},
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
WebSearch { id: String, query: String },
WebSearch {
id: String,
query: String,
#[ts(type = "number | null")]
elapsed_ms: Option<i64>,
},
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
ImageView { id: String, path: String },
ImageView {
id: String,
path: String,
#[ts(type = "number | null")]
elapsed_ms: Option<i64>,
},
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
EnteredReviewMode { id: String, review: String },
EnteredReviewMode {
id: String,
review: String,
#[ts(type = "number | null")]
elapsed_ms: Option<i64>,
},
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
ExitedReviewMode { id: String, review: String },
ExitedReviewMode {
id: String,
review: String,
#[ts(type = "number | null")]
elapsed_ms: Option<i64>,
},
}
impl From<CoreTurnItem> for ThreadItem {
@@ -1885,6 +1925,7 @@ impl From<CoreTurnItem> for ThreadItem {
CoreTurnItem::UserMessage(user) => ThreadItem::UserMessage {
id: user.id,
content: user.content.into_iter().map(UserInput::from).collect(),
elapsed_ms: None,
},
CoreTurnItem::AgentMessage(agent) => {
let text = agent
@@ -1894,16 +1935,22 @@ impl From<CoreTurnItem> for ThreadItem {
CoreAgentMessageContent::Text { text } => text,
})
.collect::<String>();
ThreadItem::AgentMessage { id: agent.id, text }
ThreadItem::AgentMessage {
id: agent.id,
text,
elapsed_ms: None,
}
}
CoreTurnItem::Reasoning(reasoning) => ThreadItem::Reasoning {
id: reasoning.id,
summary: reasoning.summary_text,
content: reasoning.raw_content,
elapsed_ms: None,
},
CoreTurnItem::WebSearch(search) => ThreadItem::WebSearch {
id: search.id,
query: search.query,
elapsed_ms: None,
},
}
}
@@ -2548,6 +2595,7 @@ mod tests {
path: PathBuf::from("/repo/.codex/skills/skill-creator/SKILL.md"),
},
],
elapsed_ms: None,
}
);
@@ -2568,6 +2616,7 @@ mod tests {
ThreadItem::AgentMessage {
id: "agent-1".to_string(),
text: "Hello world".to_string(),
elapsed_ms: None,
}
);
@@ -2583,6 +2632,7 @@ mod tests {
id: "reasoning-1".to_string(),
summary: vec!["line one".to_string(), "line two".to_string()],
content: vec![],
elapsed_ms: None,
}
);
@@ -2596,6 +2646,7 @@ mod tests {
ThreadItem::WebSearch {
id: "search-1".to_string(),
query: "docs".to_string(),
elapsed_ms: None,
}
);
}