mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
[codex] Assign response item IDs when recording history (#28814)
## Why Client-created response items enter history without IDs, so their identity is lost across rollout persistence and resume. IDs should be assigned once at the history-recording boundary, while IDs returned by the server must remain unchanged. The Responses API validates item IDs using type-specific prefixes. Locally generated IDs therefore use the matching prefix plus a hyphenated UUIDv7, keeping them valid while distinguishable from server-generated IDs. Because this changes persisted history and provider request shapes, the behavior is opt-in behind the under-development `item_ids` feature. Compaction triggers remain request controls whose API shape does not accept an ID. ## What changed - Register the disabled-by-default `item_ids` feature and expose it in `config.schema.json`. - Make supported optional `ResponseItem` IDs serializable and expose them in the generated app-server schemas. - When `item_ids` is enabled, assign an ID during conversation-history preparation if an item has no ID. - Generate type-prefixed, hyphenated UUIDv7 IDs using the Responses API item conventions. - Preserve existing server IDs without rewriting them. - Persist assigned IDs in rollouts and include them in subsequent Responses requests. - Remove the unsupported ID field from `CompactionTrigger` and document why it has no ID. - Add integration coverage for enabled ID persistence, preservation of server IDs, and omission of generated IDs while the feature is disabled. `prepare_conversation_items_for_history` is the single response-item ID allocation boundary. ## Test plan - `just test -p codex-features` - `just test -p codex-core response_item_ids_persist_across_resume_and_preserve_server_ids` - `just test -p codex-core non_openai_responses_requests_omit_item_turn_metadata` - `just test -p codex-core resize_all_images_prepares_failures_before_history_insertion` - `just test -p codex-protocol` - `just test -p codex-app-server-protocol` - `just test -p codex-api azure_default_store_attaches_ids_and_headers`
This commit is contained in:
@@ -9,7 +9,6 @@ use codex_protocol::models::ResponseItem;
|
||||
use http::HeaderMap;
|
||||
use http::Method;
|
||||
use serde::Deserialize;
|
||||
use serde_json::to_value;
|
||||
use std::sync::Arc;
|
||||
use std::sync::OnceLock;
|
||||
use std::time::Duration;
|
||||
@@ -76,7 +75,7 @@ impl<T: HttpTransport> CompactClient<T> {
|
||||
request_timeout: Duration,
|
||||
turn_state: Option<&OnceLock<String>>,
|
||||
) -> Result<Vec<ResponseItem>, ApiError> {
|
||||
let body = to_value(input)
|
||||
let body = serde_json::to_value(input)
|
||||
.map_err(|e| ApiError::Stream(format!("failed to encode compaction input: {e}")))?;
|
||||
self.compact(body, extra_headers, request_timeout, turn_state)
|
||||
.await
|
||||
|
||||
@@ -5,7 +5,6 @@ use crate::endpoint::session::EndpointSession;
|
||||
use crate::error::ApiError;
|
||||
use crate::provider::Provider;
|
||||
use crate::requests::Compression;
|
||||
use crate::requests::attach_item_ids;
|
||||
use crate::requests::headers::build_session_headers;
|
||||
use crate::requests::headers::insert_header;
|
||||
use crate::requests::headers::subagent_header;
|
||||
@@ -82,16 +81,8 @@ impl<T: HttpTransport> ResponsesClient<T> {
|
||||
turn_state,
|
||||
} = options;
|
||||
|
||||
let body = if request.store && self.session.provider().is_azure_responses_endpoint() {
|
||||
let mut body = serde_json::to_value(&request).map_err(|e| {
|
||||
ApiError::Stream(format!("failed to encode responses request: {e}"))
|
||||
})?;
|
||||
attach_item_ids(&mut body, &request.input);
|
||||
EncodedJsonBody::encode(&body)
|
||||
} else {
|
||||
EncodedJsonBody::encode(&request)
|
||||
}
|
||||
.map_err(|e| ApiError::Stream(format!("failed to encode responses request: {e}")))?;
|
||||
let body = EncodedJsonBody::encode(&request)
|
||||
.map_err(|e| ApiError::Stream(format!("failed to encode responses request: {e}")))?;
|
||||
|
||||
let mut headers = extra_headers;
|
||||
if let Some(ref thread_id) = thread_id {
|
||||
|
||||
@@ -149,7 +149,7 @@ mod tests {
|
||||
model: "gpt-test".to_string(),
|
||||
reasoning: None,
|
||||
input: Some(SearchInput::Items(vec![ResponseItem::Message {
|
||||
id: None,
|
||||
id: Some("msg_search".to_string()),
|
||||
role: "user".to_string(),
|
||||
content: vec![
|
||||
ContentItem::InputText {
|
||||
@@ -228,6 +228,7 @@ mod tests {
|
||||
"model": "gpt-test",
|
||||
"input": [{
|
||||
"type": "message",
|
||||
"id": "msg_search",
|
||||
"role": "user",
|
||||
"content": [
|
||||
{"type": "input_text", "text": "find this"},
|
||||
|
||||
@@ -2,4 +2,3 @@ pub(crate) mod headers;
|
||||
pub(crate) mod responses;
|
||||
|
||||
pub use responses::Compression;
|
||||
pub(crate) use responses::attach_item_ids;
|
||||
|
||||
@@ -1,37 +1,6 @@
|
||||
use codex_protocol::models::ResponseItem;
|
||||
use serde_json::Value;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
pub enum Compression {
|
||||
#[default]
|
||||
None,
|
||||
Zstd,
|
||||
}
|
||||
|
||||
pub(crate) fn attach_item_ids(payload_json: &mut Value, original_items: &[ResponseItem]) {
|
||||
let Some(input_value) = payload_json.get_mut("input") else {
|
||||
return;
|
||||
};
|
||||
let Value::Array(items) = input_value else {
|
||||
return;
|
||||
};
|
||||
|
||||
for (value, item) in items.iter_mut().zip(original_items.iter()) {
|
||||
if let ResponseItem::Reasoning { id: Some(id), .. }
|
||||
| ResponseItem::Message { id: Some(id), .. }
|
||||
| ResponseItem::WebSearchCall { id: Some(id), .. }
|
||||
| ResponseItem::FunctionCall { id: Some(id), .. }
|
||||
| ResponseItem::ToolSearchCall { id: Some(id), .. }
|
||||
| ResponseItem::LocalShellCall { id: Some(id), .. }
|
||||
| ResponseItem::CustomToolCall { id: Some(id), .. } = item
|
||||
{
|
||||
if id.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(obj) = value.as_object_mut() {
|
||||
obj.insert("id".to_string(), Value::String(id.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user