Restore phase when loading from history (#12244)
This commit is contained in:
parent
f2d5842ed1
commit
3a951f8096
15 changed files with 144 additions and 9 deletions
|
|
@ -649,6 +649,17 @@
|
|||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"phase": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/MessagePhase"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"agent_message"
|
||||
|
|
@ -5596,6 +5607,17 @@
|
|||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"phase": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/MessagePhase"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"agent_message"
|
||||
|
|
|
|||
|
|
@ -1465,6 +1465,17 @@
|
|||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"phase": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/MessagePhase2"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"agent_message"
|
||||
|
|
|
|||
|
|
@ -2693,6 +2693,17 @@
|
|||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"phase": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/v2/MessagePhase"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"agent_message"
|
||||
|
|
|
|||
|
|
@ -649,6 +649,17 @@
|
|||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"phase": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/MessagePhase"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"agent_message"
|
||||
|
|
|
|||
|
|
@ -649,6 +649,17 @@
|
|||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"phase": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/MessagePhase"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"agent_message"
|
||||
|
|
|
|||
|
|
@ -649,6 +649,17 @@
|
|||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"phase": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/MessagePhase"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"agent_message"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { MessagePhase } from "./MessagePhase";
|
||||
|
||||
export type AgentMessageEvent = { message: string, };
|
||||
export type AgentMessageEvent = { message: string, phase: MessagePhase | null, };
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ use crate::protocol::v2::TurnError;
|
|||
use crate::protocol::v2::TurnStatus;
|
||||
use crate::protocol::v2::UserInput;
|
||||
use crate::protocol::v2::WebSearchAction;
|
||||
use codex_protocol::models::MessagePhase as CoreMessagePhase;
|
||||
use codex_protocol::protocol::AgentReasoningEvent;
|
||||
use codex_protocol::protocol::AgentReasoningRawContentEvent;
|
||||
use codex_protocol::protocol::AgentStatus;
|
||||
|
|
@ -81,7 +82,9 @@ impl ThreadHistoryBuilder {
|
|||
fn handle_event(&mut self, event: &EventMsg) {
|
||||
match event {
|
||||
EventMsg::UserMessage(payload) => self.handle_user_message(payload),
|
||||
EventMsg::AgentMessage(payload) => self.handle_agent_message(payload.message.clone()),
|
||||
EventMsg::AgentMessage(payload) => {
|
||||
self.handle_agent_message(payload.message.clone(), payload.phase.clone())
|
||||
}
|
||||
EventMsg::AgentReasoning(payload) => self.handle_agent_reasoning(payload),
|
||||
EventMsg::AgentReasoningRawContent(payload) => {
|
||||
self.handle_agent_reasoning_raw_content(payload)
|
||||
|
|
@ -143,7 +146,7 @@ impl ThreadHistoryBuilder {
|
|||
self.current_turn = Some(turn);
|
||||
}
|
||||
|
||||
fn handle_agent_message(&mut self, text: String) {
|
||||
fn handle_agent_message(&mut self, text: String, phase: Option<CoreMessagePhase>) {
|
||||
if text.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
|
@ -152,7 +155,7 @@ impl ThreadHistoryBuilder {
|
|||
self.ensure_turn().items.push(ThreadItem::AgentMessage {
|
||||
id,
|
||||
text,
|
||||
phase: None,
|
||||
phase: phase.map(Into::into),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -758,6 +761,7 @@ impl From<PendingTurn> for Turn {
|
|||
mod tests {
|
||||
use super::*;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::models::MessagePhase as CoreMessagePhase;
|
||||
use codex_protocol::models::WebSearchAction as CoreWebSearchAction;
|
||||
use codex_protocol::parse_command::ParsedCommand;
|
||||
use codex_protocol::protocol::AgentMessageEvent;
|
||||
|
|
@ -792,6 +796,7 @@ mod tests {
|
|||
}),
|
||||
EventMsg::AgentMessage(AgentMessageEvent {
|
||||
message: "Hi there".into(),
|
||||
phase: None,
|
||||
}),
|
||||
EventMsg::AgentReasoning(AgentReasoningEvent {
|
||||
text: "thinking".into(),
|
||||
|
|
@ -807,6 +812,7 @@ mod tests {
|
|||
}),
|
||||
EventMsg::AgentMessage(AgentMessageEvent {
|
||||
message: "Reply two".into(),
|
||||
phase: None,
|
||||
}),
|
||||
];
|
||||
|
||||
|
|
@ -877,6 +883,29 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preserves_agent_message_phase_in_history() {
|
||||
let events = vec![EventMsg::AgentMessage(AgentMessageEvent {
|
||||
message: "Final reply".into(),
|
||||
phase: Some(CoreMessagePhase::FinalAnswer),
|
||||
})];
|
||||
|
||||
let items = events
|
||||
.into_iter()
|
||||
.map(RolloutItem::EventMsg)
|
||||
.collect::<Vec<_>>();
|
||||
let turns = build_turns_from_rollout_items(&items);
|
||||
assert_eq!(turns.len(), 1);
|
||||
assert_eq!(
|
||||
turns[0].items[0],
|
||||
ThreadItem::AgentMessage {
|
||||
id: "item-1".into(),
|
||||
text: "Final reply".into(),
|
||||
phase: Some(crate::protocol::v2::MessagePhase::FinalAnswer),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn splits_reasoning_when_interleaved() {
|
||||
let events = vec![
|
||||
|
|
@ -894,6 +923,7 @@ mod tests {
|
|||
}),
|
||||
EventMsg::AgentMessage(AgentMessageEvent {
|
||||
message: "interlude".into(),
|
||||
phase: None,
|
||||
}),
|
||||
EventMsg::AgentReasoning(AgentReasoningEvent {
|
||||
text: "second summary".into(),
|
||||
|
|
@ -938,6 +968,7 @@ mod tests {
|
|||
}),
|
||||
EventMsg::AgentMessage(AgentMessageEvent {
|
||||
message: "Working...".into(),
|
||||
phase: None,
|
||||
}),
|
||||
EventMsg::TurnAborted(TurnAbortedEvent {
|
||||
turn_id: Some("turn-1".into()),
|
||||
|
|
@ -951,6 +982,7 @@ mod tests {
|
|||
}),
|
||||
EventMsg::AgentMessage(AgentMessageEvent {
|
||||
message: "Second attempt complete.".into(),
|
||||
phase: None,
|
||||
}),
|
||||
];
|
||||
|
||||
|
|
@ -1017,6 +1049,7 @@ mod tests {
|
|||
}),
|
||||
EventMsg::AgentMessage(AgentMessageEvent {
|
||||
message: "A1".into(),
|
||||
phase: None,
|
||||
}),
|
||||
EventMsg::UserMessage(UserMessageEvent {
|
||||
message: "Second".into(),
|
||||
|
|
@ -1026,6 +1059,7 @@ mod tests {
|
|||
}),
|
||||
EventMsg::AgentMessage(AgentMessageEvent {
|
||||
message: "A2".into(),
|
||||
phase: None,
|
||||
}),
|
||||
EventMsg::ThreadRolledBack(ThreadRolledBackEvent { num_turns: 1 }),
|
||||
EventMsg::UserMessage(UserMessageEvent {
|
||||
|
|
@ -1036,6 +1070,7 @@ mod tests {
|
|||
}),
|
||||
EventMsg::AgentMessage(AgentMessageEvent {
|
||||
message: "A3".into(),
|
||||
phase: None,
|
||||
}),
|
||||
];
|
||||
|
||||
|
|
@ -1097,6 +1132,7 @@ mod tests {
|
|||
}),
|
||||
EventMsg::AgentMessage(AgentMessageEvent {
|
||||
message: "A1".into(),
|
||||
phase: None,
|
||||
}),
|
||||
EventMsg::UserMessage(UserMessageEvent {
|
||||
message: "Two".into(),
|
||||
|
|
@ -1106,6 +1142,7 @@ mod tests {
|
|||
}),
|
||||
EventMsg::AgentMessage(AgentMessageEvent {
|
||||
message: "A2".into(),
|
||||
phase: None,
|
||||
}),
|
||||
EventMsg::ThreadRolledBack(ThreadRolledBackEvent { num_turns: 99 }),
|
||||
];
|
||||
|
|
@ -1469,6 +1506,7 @@ mod tests {
|
|||
}),
|
||||
EventMsg::AgentMessage(AgentMessageEvent {
|
||||
message: "still in b".into(),
|
||||
phase: None,
|
||||
}),
|
||||
EventMsg::TurnComplete(TurnCompleteEvent {
|
||||
turn_id: "turn-b".into(),
|
||||
|
|
@ -1522,6 +1560,7 @@ mod tests {
|
|||
}),
|
||||
EventMsg::AgentMessage(AgentMessageEvent {
|
||||
message: "still in b".into(),
|
||||
phase: None,
|
||||
}),
|
||||
];
|
||||
|
||||
|
|
@ -1626,6 +1665,7 @@ mod tests {
|
|||
}),
|
||||
EventMsg::AgentMessage(AgentMessageEvent {
|
||||
message: "done".into(),
|
||||
phase: None,
|
||||
}),
|
||||
EventMsg::Error(ErrorEvent {
|
||||
message: "rollback failed".into(),
|
||||
|
|
|
|||
|
|
@ -1073,6 +1073,7 @@ mod tests {
|
|||
.record_items(&[RolloutItem::EventMsg(EventMsg::AgentMessage(
|
||||
AgentMessageEvent {
|
||||
message: "buffered-event".to_string(),
|
||||
phase: None,
|
||||
},
|
||||
))])
|
||||
.await?;
|
||||
|
|
|
|||
|
|
@ -299,7 +299,7 @@ impl EventProcessor for EventProcessorWithHumanOutput {
|
|||
);
|
||||
}
|
||||
}
|
||||
EventMsg::AgentMessage(AgentMessageEvent { message }) => {
|
||||
EventMsg::AgentMessage(AgentMessageEvent { message, .. }) => {
|
||||
ts_msg!(
|
||||
self,
|
||||
"{}\n{}",
|
||||
|
|
|
|||
|
|
@ -738,6 +738,7 @@ fn agent_message_produces_item_completed_agent_message() {
|
|||
"e1",
|
||||
EventMsg::AgentMessage(AgentMessageEvent {
|
||||
message: "hello".to_string(),
|
||||
phase: None,
|
||||
}),
|
||||
);
|
||||
let out = ep.collect_thread_events(&ev);
|
||||
|
|
|
|||
|
|
@ -190,13 +190,12 @@ impl AgentMessageItem {
|
|||
}
|
||||
|
||||
pub fn as_legacy_events(&self) -> Vec<EventMsg> {
|
||||
// Legacy events only preserve visible assistant text; `phase` has no
|
||||
// representation in the v1 event stream.
|
||||
self.content
|
||||
.iter()
|
||||
.map(|c| match c {
|
||||
AgentMessageContent::Text { text } => EventMsg::AgentMessage(AgentMessageEvent {
|
||||
message: text.clone(),
|
||||
phase: self.phase.clone(),
|
||||
}),
|
||||
})
|
||||
.collect()
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ use crate::mcp::Tool as McpTool;
|
|||
use crate::message_history::HistoryEntry;
|
||||
use crate::models::BaseInstructions;
|
||||
use crate::models::ContentItem;
|
||||
use crate::models::MessagePhase;
|
||||
use crate::models::ResponseItem;
|
||||
use crate::models::WebSearchAction;
|
||||
use crate::num_format::format_with_separators;
|
||||
|
|
@ -1580,6 +1581,8 @@ impl fmt::Display for FinalOutput {
|
|||
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
||||
pub struct AgentMessageEvent {
|
||||
pub message: String,
|
||||
#[serde(default)]
|
||||
pub phase: Option<MessagePhase>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
||||
|
|
|
|||
|
|
@ -3986,7 +3986,9 @@ impl ChatWidget {
|
|||
match msg {
|
||||
EventMsg::SessionConfigured(e) => self.on_session_configured(e),
|
||||
EventMsg::ThreadNameUpdated(e) => self.on_thread_name_updated(e),
|
||||
EventMsg::AgentMessage(AgentMessageEvent { message }) => self.on_agent_message(message),
|
||||
EventMsg::AgentMessage(AgentMessageEvent { message, .. }) => {
|
||||
self.on_agent_message(message)
|
||||
}
|
||||
EventMsg::AgentMessageDelta(AgentMessageDeltaEvent { delta }) => {
|
||||
self.on_agent_message_delta(delta)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -169,6 +169,7 @@ async fn resumed_initial_messages_render_history() {
|
|||
}),
|
||||
EventMsg::AgentMessage(AgentMessageEvent {
|
||||
message: "assistant reply".to_string(),
|
||||
phase: None,
|
||||
}),
|
||||
]),
|
||||
network_proxy: None,
|
||||
|
|
@ -3322,6 +3323,7 @@ async fn unified_exec_wait_after_final_agent_message_snapshot() {
|
|||
id: "turn-1".into(),
|
||||
msg: EventMsg::AgentMessage(AgentMessageEvent {
|
||||
message: "Final response.".into(),
|
||||
phase: None,
|
||||
}),
|
||||
});
|
||||
chat.handle_codex_event(Event {
|
||||
|
|
@ -7013,6 +7015,7 @@ async fn multiple_agent_messages_in_single_turn_emit_multiple_headers() {
|
|||
id: "s1".into(),
|
||||
msg: EventMsg::AgentMessage(AgentMessageEvent {
|
||||
message: "First message".into(),
|
||||
phase: None,
|
||||
}),
|
||||
});
|
||||
|
||||
|
|
@ -7021,6 +7024,7 @@ async fn multiple_agent_messages_in_single_turn_emit_multiple_headers() {
|
|||
id: "s1".into(),
|
||||
msg: EventMsg::AgentMessage(AgentMessageEvent {
|
||||
message: "Second message".into(),
|
||||
phase: None,
|
||||
}),
|
||||
});
|
||||
|
||||
|
|
@ -7066,6 +7070,7 @@ async fn final_reasoning_then_message_without_deltas_are_rendered() {
|
|||
id: "s1".into(),
|
||||
msg: EventMsg::AgentMessage(AgentMessageEvent {
|
||||
message: "Here is the result.".into(),
|
||||
phase: None,
|
||||
}),
|
||||
});
|
||||
|
||||
|
|
@ -7126,6 +7131,7 @@ async fn deltas_then_same_final_message_are_rendered_snapshot() {
|
|||
id: "s1".into(),
|
||||
msg: EventMsg::AgentMessage(AgentMessageEvent {
|
||||
message: "Here is the result.".into(),
|
||||
phase: None,
|
||||
}),
|
||||
});
|
||||
|
||||
|
|
@ -7147,7 +7153,12 @@ async fn chatwidget_exec_and_status_layout_vt100_snapshot() {
|
|||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await;
|
||||
chat.handle_codex_event(Event {
|
||||
id: "t1".into(),
|
||||
msg: EventMsg::AgentMessage(AgentMessageEvent { message: "I’m going to search the repo for where “Change Approved” is rendered to update that view.".into() }),
|
||||
msg: EventMsg::AgentMessage(AgentMessageEvent {
|
||||
message:
|
||||
"I’m going to search the repo for where “Change Approved” is rendered to update that view."
|
||||
.into(),
|
||||
phase: None,
|
||||
}),
|
||||
});
|
||||
|
||||
let command = vec!["bash".into(), "-lc".into(), "rg \"Change Approved\"".into()];
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue