mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
feat(core): store turn_id on ResponseItem metadata (#28360)
## Description This PR is a followup to https://github.com/openai/codex/pull/28355 and starts assigning `internal_chat_message_metadata_passthrough.turn_id` to durable Responses API items created during a turn. The goal is that those items keep the `turn_id` that introduced them when Codex resends stateless HTTP context, reconstructs history for resume/fork paths, or reuses websocket response state. ## What changed - Set `internal_chat_message_metadata_passthrough.turn_id` when missing as response items enter durable history, initial/replacement history, inter-agent communication history, and local compaction summaries. - Preserve existing item turn IDs instead of overwriting them during persistence, resume reconstruction, compaction, forked history, and websocket incremental reuse. - Keep `compaction_trigger` fieldless because it is a request control, not a durable response item. - Update focused history/request assertions and fixtures for stateless requests, websocket incrementals, compaction, thread injection, prompt debug, and related CI coverage.
This commit is contained in:
committed by
GitHub
Unverified
parent
7153affa0f
commit
4a82ecc3c9
@@ -915,6 +915,20 @@ pub struct InternalChatMessageMetadataPassthrough {
|
||||
pub turn_id: Option<String>,
|
||||
}
|
||||
|
||||
impl InternalChatMessageMetadataPassthrough {
|
||||
pub(crate) fn set_turn_id_if_missing(metadata: &mut Option<Self>, turn_id: &str) {
|
||||
if turn_id.is_empty()
|
||||
|| metadata
|
||||
.as_ref()
|
||||
.and_then(|metadata| metadata.turn_id.as_deref())
|
||||
.is_some_and(|turn_id| !turn_id.is_empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
metadata.get_or_insert_with(Self::default).turn_id = Some(turn_id.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema, TS)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
pub enum ResponseItem {
|
||||
@@ -1120,13 +1134,8 @@ pub enum ResponseItem {
|
||||
#[ts(optional)]
|
||||
internal_chat_message_metadata_passthrough: Option<InternalChatMessageMetadataPassthrough>,
|
||||
},
|
||||
// Compaction triggers are request controls, and the Responses API does not
|
||||
// accept an `id` field for them.
|
||||
CompactionTrigger {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
internal_chat_message_metadata_passthrough: Option<InternalChatMessageMetadataPassthrough>,
|
||||
},
|
||||
// Compaction triggers are request controls, not durable response items.
|
||||
CompactionTrigger {},
|
||||
ContextCompaction {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
@@ -1198,16 +1207,11 @@ impl ResponseItem {
|
||||
}
|
||||
|
||||
/// Stamps the item with `turn_id` unless it already has a non-empty turn ID.
|
||||
pub fn stamp_turn_id_if_missing(&mut self, turn_id: &str) {
|
||||
if turn_id.is_empty() || self.turn_id().is_some() {
|
||||
return;
|
||||
}
|
||||
pub fn set_turn_id_if_missing(&mut self, turn_id: &str) {
|
||||
let Some(metadata) = self.internal_chat_message_metadata_passthrough_mut() else {
|
||||
return;
|
||||
};
|
||||
metadata
|
||||
.get_or_insert_with(InternalChatMessageMetadataPassthrough::default)
|
||||
.turn_id = Some(turn_id.to_string());
|
||||
InternalChatMessageMetadataPassthrough::set_turn_id_if_missing(metadata, turn_id);
|
||||
}
|
||||
|
||||
/// Removes internal chat message metadata passthrough before sending to a provider that does
|
||||
@@ -1274,15 +1278,11 @@ impl ResponseItem {
|
||||
internal_chat_message_metadata_passthrough: metadata,
|
||||
..
|
||||
}
|
||||
| Self::CompactionTrigger {
|
||||
internal_chat_message_metadata_passthrough: metadata,
|
||||
..
|
||||
}
|
||||
| Self::ContextCompaction {
|
||||
internal_chat_message_metadata_passthrough: metadata,
|
||||
..
|
||||
} => metadata.as_ref(),
|
||||
Self::Other => None,
|
||||
Self::CompactionTrigger { .. } | Self::Other => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1342,15 +1342,11 @@ impl ResponseItem {
|
||||
internal_chat_message_metadata_passthrough: metadata,
|
||||
..
|
||||
}
|
||||
| Self::CompactionTrigger {
|
||||
internal_chat_message_metadata_passthrough: metadata,
|
||||
..
|
||||
}
|
||||
| Self::ContextCompaction {
|
||||
internal_chat_message_metadata_passthrough: metadata,
|
||||
..
|
||||
} => Some(metadata),
|
||||
Self::Other => None,
|
||||
Self::CompactionTrigger { .. } | Self::Other => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2206,23 +2202,23 @@ mod tests {
|
||||
}))?;
|
||||
assert_eq!(unknown_metadata, item);
|
||||
|
||||
item.stamp_turn_id_if_missing("turn-2");
|
||||
item.set_turn_id_if_missing("turn-2");
|
||||
assert_eq!(item.turn_id(), Some("turn-1"));
|
||||
|
||||
let mut empty_turn_id =
|
||||
response_item_with_passthrough_metadata(Some(passthrough_metadata("")));
|
||||
empty_turn_id.stamp_turn_id_if_missing("turn-1");
|
||||
empty_turn_id.set_turn_id_if_missing("turn-1");
|
||||
assert_eq!(empty_turn_id.turn_id(), Some("turn-1"));
|
||||
|
||||
let mut missing_turn_id = response_item_with_passthrough_metadata(
|
||||
/*internal_chat_message_metadata_passthrough*/ None,
|
||||
);
|
||||
missing_turn_id.stamp_turn_id_if_missing("");
|
||||
missing_turn_id.stamp_turn_id_if_missing("turn-1");
|
||||
missing_turn_id.set_turn_id_if_missing("");
|
||||
missing_turn_id.set_turn_id_if_missing("turn-1");
|
||||
assert_eq!(missing_turn_id.turn_id(), Some("turn-1"));
|
||||
|
||||
let mut other = ResponseItem::Other;
|
||||
other.stamp_turn_id_if_missing("turn-1");
|
||||
other.set_turn_id_if_missing("turn-1");
|
||||
assert_eq!(other.turn_id(), None);
|
||||
Ok(())
|
||||
}
|
||||
@@ -3111,9 +3107,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn serializes_compaction_trigger_without_payload() -> Result<()> {
|
||||
let item = ResponseItem::CompactionTrigger {
|
||||
internal_chat_message_metadata_passthrough: None,
|
||||
};
|
||||
let item = ResponseItem::CompactionTrigger {};
|
||||
|
||||
assert_eq!(
|
||||
serde_json::to_value(item)?,
|
||||
@@ -3124,37 +3118,13 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serializes_stamped_compaction_trigger_passthrough_metadata() -> Result<()> {
|
||||
let mut item = ResponseItem::CompactionTrigger {
|
||||
internal_chat_message_metadata_passthrough: None,
|
||||
};
|
||||
item.stamp_turn_id_if_missing("turn-1");
|
||||
|
||||
assert_eq!(
|
||||
serde_json::to_value(item)?,
|
||||
serde_json::json!({
|
||||
"type": "compaction_trigger",
|
||||
"internal_chat_message_metadata_passthrough": {
|
||||
"turn_id": "turn-1",
|
||||
},
|
||||
})
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserializes_compaction_trigger_without_payload() -> Result<()> {
|
||||
let json = r#"{"type":"compaction_trigger"}"#;
|
||||
|
||||
let item: ResponseItem = serde_json::from_str(json)?;
|
||||
|
||||
assert_eq!(
|
||||
item,
|
||||
ResponseItem::CompactionTrigger {
|
||||
internal_chat_message_metadata_passthrough: None,
|
||||
}
|
||||
);
|
||||
assert_eq!(item, ResponseItem::CompactionTrigger {});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user