mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Preserve assistant phase for replayed messages (#19832)
This commit is contained in:
committed by
GitHub
Unverified
parent
21e19912e0
commit
598bbcdb58
@@ -456,9 +456,15 @@ impl CodexThread {
|
||||
|
||||
fn pending_message_input_item(message: &ResponseItem) -> CodexResult<ResponseInputItem> {
|
||||
match message {
|
||||
ResponseItem::Message { role, content, .. } => Ok(ResponseInputItem::Message {
|
||||
ResponseItem::Message {
|
||||
role,
|
||||
content,
|
||||
phase,
|
||||
..
|
||||
} => Ok(ResponseInputItem::Message {
|
||||
role: role.clone(),
|
||||
content: content.clone(),
|
||||
phase: phase.clone(),
|
||||
}),
|
||||
_ => Err(CodexErr::InvalidRequest(
|
||||
"append_message only supports ResponseItem::Message".to_string(),
|
||||
|
||||
@@ -1307,6 +1307,7 @@ impl Session {
|
||||
content: vec![ContentItem::InputText {
|
||||
text: continuation_prompt(&goal),
|
||||
}],
|
||||
phase: None,
|
||||
}],
|
||||
})
|
||||
}
|
||||
@@ -1452,6 +1453,7 @@ fn budget_limit_steering_item(goal: &ThreadGoal) -> ResponseInputItem {
|
||||
content: vec![ContentItem::InputText {
|
||||
text: budget_limit_prompt(goal),
|
||||
}],
|
||||
phase: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1205,6 +1205,7 @@ Approved action:
|
||||
let items = vec![ResponseInputItem::Message {
|
||||
role: "developer".to_string(),
|
||||
content: vec![ContentItem::InputText { text }],
|
||||
phase: None,
|
||||
}];
|
||||
|
||||
if let Err(items) = sess.inject_response_items(items).await {
|
||||
|
||||
@@ -1699,6 +1699,7 @@ impl Session {
|
||||
.inject_response_items(vec![ResponseInputItem::Message {
|
||||
role: "developer".to_string(),
|
||||
content: vec![ContentItem::InputText { text }],
|
||||
phase: None,
|
||||
}])
|
||||
.await
|
||||
.is_err()
|
||||
@@ -1795,6 +1796,7 @@ impl Session {
|
||||
.inject_response_items(vec![ResponseInputItem::Message {
|
||||
role: "developer".to_string(),
|
||||
content: vec![ContentItem::InputText { text }],
|
||||
phase: None,
|
||||
}])
|
||||
.await
|
||||
.is_err()
|
||||
|
||||
@@ -6432,6 +6432,7 @@ async fn task_finish_emits_turn_item_lifecycle_for_leftover_pending_user_input()
|
||||
content: vec![ContentItem::InputText {
|
||||
text: "late pending input".to_string(),
|
||||
}],
|
||||
phase: None,
|
||||
}])
|
||||
.await
|
||||
.expect("inject pending input into active turn");
|
||||
@@ -6677,18 +6678,21 @@ async fn prepend_pending_input_keeps_older_tail_ahead_of_newer_input() {
|
||||
content: vec![ContentItem::InputText {
|
||||
text: "blocked queued prompt".to_string(),
|
||||
}],
|
||||
phase: None,
|
||||
};
|
||||
let later = ResponseInputItem::Message {
|
||||
role: "user".to_string(),
|
||||
content: vec![ContentItem::InputText {
|
||||
text: "later queued prompt".to_string(),
|
||||
}],
|
||||
phase: None,
|
||||
};
|
||||
let newer = ResponseInputItem::Message {
|
||||
role: "user".to_string(),
|
||||
content: vec![ContentItem::InputText {
|
||||
text: "newer queued prompt".to_string(),
|
||||
}],
|
||||
phase: None,
|
||||
};
|
||||
|
||||
sess.inject_response_items(vec![blocked.clone(), later.clone()])
|
||||
@@ -6719,6 +6723,7 @@ async fn queued_response_items_for_next_turn_move_into_next_active_turn() {
|
||||
content: vec![ContentItem::InputText {
|
||||
text: "queued before wake".to_string(),
|
||||
}],
|
||||
phase: None,
|
||||
};
|
||||
|
||||
sess.queue_response_items_for_next_turn(vec![queued_item.clone()])
|
||||
@@ -6745,6 +6750,7 @@ async fn idle_interrupt_does_not_wake_queued_next_turn_items() {
|
||||
content: vec![ContentItem::InputText {
|
||||
text: "queued before interrupt".to_string(),
|
||||
}],
|
||||
phase: None,
|
||||
};
|
||||
|
||||
sess.queue_response_items_for_next_turn(vec![queued_item])
|
||||
@@ -6764,6 +6770,7 @@ async fn abort_empty_active_turn_preserves_pending_input() {
|
||||
content: vec![ContentItem::InputText {
|
||||
text: "late pending input".to_string(),
|
||||
}],
|
||||
phase: None,
|
||||
};
|
||||
let turn_state = {
|
||||
let mut active = sess.active_turn.lock().await;
|
||||
@@ -6969,7 +6976,7 @@ async fn budget_limited_accounting_steers_active_turn_without_aborting() -> anyh
|
||||
.await?;
|
||||
|
||||
let pending_input = sess.get_pending_input().await;
|
||||
let [ResponseInputItem::Message { role, content }] = pending_input.as_slice() else {
|
||||
let [ResponseInputItem::Message { role, content, .. }] = pending_input.as_slice() else {
|
||||
panic!("expected one budget-limit steering message, got {pending_input:#?}");
|
||||
};
|
||||
assert_eq!("developer", role);
|
||||
|
||||
@@ -354,7 +354,16 @@ async fn persist_user_shell_output(
|
||||
}
|
||||
|
||||
let response_input_item = match output_item {
|
||||
ResponseItem::Message { role, content, .. } => ResponseInputItem::Message { role, content },
|
||||
ResponseItem::Message {
|
||||
role,
|
||||
content,
|
||||
phase,
|
||||
..
|
||||
} => ResponseInputItem::Message {
|
||||
role,
|
||||
content,
|
||||
phase,
|
||||
},
|
||||
_ => unreachable!("user shell command output record should always be a message"),
|
||||
};
|
||||
|
||||
|
||||
@@ -609,6 +609,9 @@ pub enum ResponseInputItem {
|
||||
Message {
|
||||
role: String,
|
||||
content: Vec<ContentItem>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
phase: Option<MessagePhase>,
|
||||
},
|
||||
FunctionCallOutput {
|
||||
call_id: String,
|
||||
@@ -1034,11 +1037,15 @@ pub fn local_image_content_items_with_label_number(
|
||||
impl From<ResponseInputItem> for ResponseItem {
|
||||
fn from(item: ResponseInputItem) -> Self {
|
||||
match item {
|
||||
ResponseInputItem::Message { role, content } => Self::Message {
|
||||
ResponseInputItem::Message {
|
||||
role,
|
||||
content,
|
||||
phase,
|
||||
} => Self::Message {
|
||||
role,
|
||||
content,
|
||||
id: None,
|
||||
phase: None,
|
||||
phase,
|
||||
},
|
||||
ResponseInputItem::FunctionCallOutput { call_id, output } => {
|
||||
Self::FunctionCallOutput { call_id, output }
|
||||
@@ -1176,6 +1183,7 @@ impl From<Vec<UserInput>> for ResponseInputItem {
|
||||
UserInput::Skill { .. } | UserInput::Mention { .. } => Vec::new(), // Tool bodies are injected later in core
|
||||
})
|
||||
.collect::<Vec<ContentItem>>(),
|
||||
phase: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1581,6 +1589,29 @@ mod tests {
|
||||
use std::path::PathBuf;
|
||||
use tempfile::tempdir;
|
||||
|
||||
#[test]
|
||||
fn response_input_message_conversion_preserves_phase() {
|
||||
let item = ResponseItem::from(ResponseInputItem::Message {
|
||||
role: "assistant".to_string(),
|
||||
content: vec![ContentItem::OutputText {
|
||||
text: "still working".to_string(),
|
||||
}],
|
||||
phase: Some(MessagePhase::Commentary),
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
item,
|
||||
ResponseItem::Message {
|
||||
id: None,
|
||||
role: "assistant".to_string(),
|
||||
content: vec![ContentItem::OutputText {
|
||||
text: "still working".to_string(),
|
||||
}],
|
||||
phase: Some(MessagePhase::Commentary),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sandbox_permissions_helpers_match_documented_semantics() {
|
||||
let cases = [
|
||||
|
||||
@@ -855,6 +855,7 @@ impl InterAgentCommunication {
|
||||
content: vec![ContentItem::OutputText {
|
||||
text: serde_json::to_string(self).unwrap_or_default(),
|
||||
}],
|
||||
phase: Some(MessagePhase::Commentary),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user