feat: add memory citation to agent message (#14821)

Client side to come
This commit is contained in:
jif-oai
2026-03-18 10:03:38 +00:00
committed by GitHub
parent 0f9484dc8a
commit a265d6043e
43 changed files with 1420 additions and 40 deletions
+2
View File
@@ -7007,6 +7007,7 @@ guardian_approval = true
id: "assistant-1".to_string(),
text: "restored response".to_string(),
phase: None,
memory_citation: None,
},
],
status: TurnStatus::Completed,
@@ -7120,6 +7121,7 @@ guardian_approval = true
id: "assistant-1".to_string(),
text: "restored response".to_string(),
phase: None,
memory_citation: None,
},
],
status: TurnStatus::Completed,
@@ -668,13 +668,33 @@ fn thread_item_to_core(item: &ThreadItem) -> Option<TurnItem> {
.map(codex_app_server_protocol::UserInput::into_core)
.collect(),
})),
ThreadItem::AgentMessage { id, text, phase } => {
Some(TurnItem::AgentMessage(AgentMessageItem {
id: id.clone(),
content: vec![AgentMessageContent::Text { text: text.clone() }],
phase: phase.clone(),
}))
}
ThreadItem::AgentMessage {
id,
text,
phase,
memory_citation,
} => Some(TurnItem::AgentMessage(AgentMessageItem {
id: id.clone(),
content: vec![AgentMessageContent::Text { text: text.clone() }],
phase: phase.clone(),
memory_citation: memory_citation.clone().map(|citation| {
codex_protocol::memory_citation::MemoryCitation {
entries: citation
.entries
.into_iter()
.map(
|entry| codex_protocol::memory_citation::MemoryCitationEntry {
path: entry.path,
line_start: entry.line_start,
line_end: entry.line_end,
note: entry.note,
},
)
.collect(),
rollout_ids: citation.thread_ids,
}
}),
})),
ThreadItem::Plan { id, text } => Some(TurnItem::Plan(PlanItem {
id: id.clone(),
text: text.clone(),
@@ -897,6 +917,7 @@ mod tests {
id: item_id,
text: "Hello from your coding assistant.".to_string(),
phase: Some(MessagePhase::FinalAnswer),
memory_citation: None,
},
thread_id: thread_id.clone(),
turn_id: turn_id.clone(),
@@ -921,13 +942,19 @@ mod tests {
);
assert_eq!(completed.turn_id, turn_id);
match &completed.item {
TurnItem::AgentMessage(AgentMessageItem { id, content, phase }) => {
TurnItem::AgentMessage(AgentMessageItem {
id,
content,
phase,
memory_citation,
}) => {
assert_eq!(id, "msg_123");
let [AgentMessageContent::Text { text }] = content.as_slice() else {
panic!("expected a single text content item");
};
assert_eq!(text, "Hello from your coding assistant.");
assert_eq!(*phase, Some(MessagePhase::FinalAnswer));
assert_eq!(*memory_citation, None);
}
_ => panic!("expected bridged agent message item"),
}
@@ -1111,6 +1138,7 @@ mod tests {
id: "assistant-1".to_string(),
text: "hi".to_string(),
phase: Some(MessagePhase::FinalAnswer),
memory_citation: None,
},
],
status: TurnStatus::Completed,
@@ -1117,6 +1117,7 @@ mod tests {
id: "assistant-1".to_string(),
text: "assistant reply".to_string(),
phase: None,
memory_citation: None,
},
],
status: TurnStatus::Completed,
@@ -202,6 +202,7 @@ async fn resumed_initial_messages_render_history() {
EventMsg::AgentMessage(AgentMessageEvent {
message: "assistant reply".to_string(),
phase: None,
memory_citation: None,
}),
]),
network_proxy: None,
@@ -250,6 +251,7 @@ async fn thread_snapshot_replay_does_not_duplicate_agent_message_history() {
text: "assistant reply".to_string(),
}],
phase: None,
memory_citation: None,
}),
}),
});
@@ -258,6 +260,7 @@ async fn thread_snapshot_replay_does_not_duplicate_agent_message_history() {
msg: EventMsg::AgentMessage(AgentMessageEvent {
message: "assistant reply".to_string(),
phase: None,
memory_citation: None,
}),
});
@@ -1546,6 +1549,7 @@ async fn live_agent_message_renders_during_review_mode() {
msg: EventMsg::AgentMessage(AgentMessageEvent {
message: "Review progress update".to_string(),
phase: None,
memory_citation: None,
}),
});
@@ -1572,6 +1576,7 @@ async fn thread_snapshot_replay_preserves_agent_message_during_review_mode() {
msg: EventMsg::AgentMessage(AgentMessageEvent {
message: "Review progress update".to_string(),
phase: None,
memory_citation: None,
}),
});
@@ -3521,6 +3526,7 @@ fn complete_assistant_message(
text: text.to_string(),
}],
phase,
memory_citation: None,
}),
}),
});
@@ -4110,6 +4116,7 @@ async fn live_legacy_agent_message_after_item_completed_does_not_duplicate_assis
msg: EventMsg::AgentMessage(AgentMessageEvent {
message: "hello".into(),
phase: Some(MessagePhase::FinalAnswer),
memory_citation: None,
}),
});
@@ -5958,6 +5965,7 @@ async fn slash_copy_is_unavailable_when_legacy_agent_message_is_not_repeated_on_
msg: EventMsg::AgentMessage(AgentMessageEvent {
message: "Legacy final message".into(),
phase: None,
memory_citation: None,
}),
});
let _ = drain_insert_history(&mut rx);
@@ -8497,11 +8505,8 @@ async fn permissions_selection_history_snapshot_full_access_to_default() {
.approval_policy
.set(AskForApproval::Never)
.expect("set approval policy");
chat.config
.permissions
.sandbox_policy
.set(SandboxPolicy::DangerFullAccess)
.expect("set sandbox policy");
chat.config.permissions.sandbox_policy =
Constrained::allow_any(SandboxPolicy::DangerFullAccess);
chat.open_permissions_popup();
let popup = render_bottom_popup(&chat, 120);
@@ -10810,6 +10815,7 @@ async fn deltas_then_same_final_message_are_rendered_snapshot() {
msg: EventMsg::AgentMessage(AgentMessageEvent {
message: "Here is the result.".into(),
phase: None,
memory_citation: None,
}),
});