Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions codex-rs/app-server-protocol/schema/typescript/index.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions codex-rs/app-server-protocol/src/protocol/thread_history.rs
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,7 @@ impl ThreadHistoryBuilder {
return;
}

let Some(hook_prompt) = parse_hook_prompt_message(id.as_ref(), content) else {
let Some(hook_prompt) = parse_hook_prompt_message(id.as_deref(), content) else {
return;
};

Expand Down Expand Up @@ -4113,7 +4113,7 @@ mod tests {
collaboration_mode_kind: Default::default(),
})),
RolloutItem::ResponseItem(codex_protocol::models::ResponseItem::Message {
id: Some("msg-1".into()),
id: Some(codex_protocol::ResponseItemId::with_suffix("msg", "1")),
role: "user".into(),
content: vec![codex_protocol::models::ContentItem::InputText {
text: "plain text".into(),
Expand Down
3 changes: 2 additions & 1 deletion codex-rs/codex-api/src/endpoint/responses_websocket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -892,6 +892,7 @@ fn serialize_websocket_request(request: &ResponsesWsRequest) -> Result<String, A
mod tests {
use super::*;
use crate::common::ResponseCreateWsRequest;
use codex_protocol::ResponseItemId;
use codex_protocol::models::ContentItem;
use codex_protocol::models::ResponseItem;
use pretty_assertions::assert_eq;
Expand All @@ -905,7 +906,7 @@ mod tests {
instructions: "Use the available tools.".to_string(),
previous_response_id: Some("resp-1".to_string()),
input: vec![ResponseItem::Message {
id: Some("msg-1".to_string()),
id: Some(ResponseItemId::with_suffix("msg", "1")),
role: "user".to_string(),
content: vec![ContentItem::InputText {
text: "hello".to_string(),
Expand Down
3 changes: 2 additions & 1 deletion codex-rs/codex-api/src/endpoint/search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ mod tests {
use codex_client::Response;
use codex_client::StreamResponse;
use codex_client::TransportError;
use codex_protocol::ResponseItemId;
use codex_protocol::models::ContentItem;
use codex_protocol::models::ResponseItem;
use http::StatusCode;
Expand Down Expand Up @@ -150,7 +151,7 @@ mod tests {
model: "gpt-test".to_string(),
reasoning: None,
input: Some(SearchInput::Items(vec![ResponseItem::Message {
id: Some("msg_search".to_string()),
id: Some(ResponseItemId::with_suffix("msg", "search")),
role: "user".to_string(),
content: vec![
ContentItem::InputText {
Expand Down
5 changes: 3 additions & 2 deletions codex-rs/codex-api/tests/clients.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use codex_client::RequestBody;
use codex_client::Response;
use codex_client::StreamResponse;
use codex_client::TransportError;
use codex_protocol::ResponseItemId;
use codex_protocol::models::ContentItem;
use codex_protocol::models::ResponseItem;
use codex_protocol::protocol::SessionSource;
Expand Down Expand Up @@ -309,7 +310,7 @@ async fn responses_client_stream_request_preserves_item_ids() -> Result<()> {
model: "gpt-test".into(),
instructions: "Say hi".into(),
input: vec![ResponseItem::Message {
id: Some("msg_1".into()),
id: Some(ResponseItemId::with_suffix("msg", "1")),
role: "user".into(),
content: vec![ContentItem::InputText { text: "hi".into() }],
phase: None,
Expand Down Expand Up @@ -516,7 +517,7 @@ async fn azure_store_sends_ids_and_headers() -> Result<()> {
model: "gpt-test".into(),
instructions: "Say hi".into(),
input: vec![ResponseItem::Message {
id: Some("msg_1".into()),
id: Some(ResponseItemId::with_suffix("msg", "1")),
role: "user".into(),
content: vec![ContentItem::InputText { text: "hi".into() }],
phase: None,
Expand Down
3 changes: 2 additions & 1 deletion codex-rs/core/src/agent/control_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use codex_features::Feature;
use codex_login::AuthManager;
use codex_login::CodexAuth;
use codex_protocol::AgentPath;
use codex_protocol::ResponseItemId;
use codex_protocol::capabilities::CapabilityRootLocation;
use codex_protocol::capabilities::SelectedCapabilityRoot;
use codex_protocol::config_types::ModeKind;
Expand Down Expand Up @@ -979,7 +980,7 @@ async fn spawn_agent_can_fork_parent_thread_history_with_sanitized_items() {
assistant_message("parent final answer", Some(MessagePhase::FinalAnswer)),
assistant_message("parent unknown phase", /*phase*/ None),
ResponseItem::Reasoning {
id: Some("parent-reasoning".to_string()),
id: Some(ResponseItemId::with_suffix("rs", "parent-reasoning")),
summary: Vec::new(),
content: None,
encrypted_content: None,
Expand Down
6 changes: 6 additions & 0 deletions codex-rs/core/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -908,6 +908,12 @@ impl ModelClient {
}

fn prepare_response_items_for_request(&self, input: &mut [ResponseItem], store: bool) {
for item in input.iter_mut() {
if item.id().is_some_and(|id| !id.is_prefixed()) {
item.set_id(/*new_id*/ None);
}
}

if self.state.item_ids_enabled || store {
return;
}
Expand Down
6 changes: 3 additions & 3 deletions codex-rs/core/src/client_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,7 @@ fn started_inference_attempt(temp: &TempDir) -> anyhow::Result<InferenceTraceAtt

fn output_message(id: &str, text: &str) -> ResponseItem {
ResponseItem::Message {
id: Some(id.to_string()),
id: Some(codex_protocol::ResponseItemId::with_suffix("msg", id)),
role: "assistant".to_string(),
content: vec![ContentItem::OutputText {
text: text.to_string(),
Expand Down Expand Up @@ -581,7 +581,7 @@ async fn dropped_response_stream_traces_cancelled_partial_output() -> anyhow::Re
// response.completed event. The harness has enough information to keep this
// item in history, so the trace should preserve it when the stream is
// abandoned.
let item = output_message("msg-1", "partial answer");
let item = output_message("1", "partial answer");
let api_stream = futures::stream::iter([Ok(ResponseEvent::OutputItemDone(item))])
.chain(futures::stream::pending());
let (mut stream, _) = super::map_response_events(
Expand Down Expand Up @@ -699,7 +699,7 @@ async fn dropped_backpressured_response_stream_traces_cancelled_partial_output()
events.push_back(ResponseEvent::Created);
}
events.push_back(ResponseEvent::OutputItemDone(output_message(
"msg-1",
"1",
"partial answer",
)));
let api_stream = NotifyAfterEventStream {
Expand Down
3 changes: 2 additions & 1 deletion codex-rs/core/src/compact_remote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -434,13 +434,14 @@ fn rewritten_output_for_context_window(item: &ResponseItem) -> Option<ResponseIt
internal_chat_message_metadata_passthrough: metadata.clone(),
},
ResponseItem::ToolSearchOutput {
id,
call_id,
status,
execution,
internal_chat_message_metadata_passthrough: metadata,
..
} => ResponseItem::ToolSearchOutput {
id: item.id().map(str::to_string),
id: id.clone(),
call_id: call_id.clone(),
status: status.clone(),
execution: execution.clone(),
Expand Down
5 changes: 3 additions & 2 deletions codex-rs/core/src/compact_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use super::*;
use crate::session::tests::build_world_state_from_turn_context;
use codex_model_provider_info::ModelProviderInfo;
use codex_model_provider_info::WireApi;
use codex_protocol::ResponseItemId;
use codex_protocol::models::DEFAULT_IMAGE_DETAIL;
use codex_protocol::models::InternalChatMessageMetadataPassthrough;
use pretty_assertions::assert_eq;
Expand Down Expand Up @@ -85,7 +86,7 @@ fn content_items_to_text_ignores_image_only_content() {
fn collect_user_messages_extracts_user_text_only() {
let items = vec![
ResponseItem::Message {
id: Some("assistant".to_string()),
id: Some(ResponseItemId::with_suffix("msg", "assistant")),
role: "assistant".to_string(),
content: vec![ContentItem::OutputText {
text: "ignored".to_string(),
Expand All @@ -94,7 +95,7 @@ fn collect_user_messages_extracts_user_text_only() {
internal_chat_message_metadata_passthrough: None,
},
ResponseItem::Message {
id: Some("user".to_string()),
id: Some(ResponseItemId::with_suffix("msg", "user")),
role: "user".to_string(),
content: vec![ContentItem::InputText {
text: "first".to_string(),
Expand Down
2 changes: 1 addition & 1 deletion codex-rs/core/src/context/contextual_user_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ pub(crate) fn is_contextual_user_fragment(content_item: &ContentItem) -> bool {
}

pub(crate) fn parse_visible_hook_prompt_message(
id: Option<&String>,
id: Option<&str>,
content: &[ContentItem],
) -> Option<HookPromptItem> {
let mut fragments = Vec::new();
Expand Down
13 changes: 7 additions & 6 deletions codex-rs/core/src/context_manager/history_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::context::world_state::WorldStateSection;
use base64::Engine;
use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
use codex_protocol::AgentPath;
use codex_protocol::ResponseItemId;
use codex_protocol::models::BaseInstructions;
use codex_protocol::models::ContentItem;
use codex_protocol::models::DEFAULT_IMAGE_DETAIL;
Expand Down Expand Up @@ -639,7 +640,7 @@ fn for_prompt_strips_images_when_model_does_not_support_images() {
fn for_prompt_preserves_image_generation_calls_when_images_are_supported() {
let history = create_history_with_items(vec![
ResponseItem::ImageGenerationCall {
id: Some("ig_123".to_string()),
id: Some(ResponseItemId::with_suffix("ig", "123")),
status: "generating".to_string(),
revised_prompt: Some("lobster".to_string()),
result: "Zm9v".to_string(),
Expand All @@ -660,7 +661,7 @@ fn for_prompt_preserves_image_generation_calls_when_images_are_supported() {
history.for_prompt(&default_input_modalities()),
vec![
ResponseItem::ImageGenerationCall {
id: Some("ig_123".to_string()),
id: Some(ResponseItemId::with_suffix("ig", "123")),
status: "generating".to_string(),
revised_prompt: Some("lobster".to_string()),
result: "Zm9v".to_string(),
Expand Down Expand Up @@ -692,7 +693,7 @@ fn for_prompt_clears_image_generation_result_when_images_are_unsupported() {
internal_chat_message_metadata_passthrough: None,
},
ResponseItem::ImageGenerationCall {
id: Some("ig_123".to_string()),
id: Some(ResponseItemId::with_suffix("ig", "123")),
status: "completed".to_string(),
revised_prompt: Some("lobster".to_string()),
result: "Zm9v".to_string(),
Expand All @@ -713,7 +714,7 @@ fn for_prompt_clears_image_generation_result_when_images_are_unsupported() {
internal_chat_message_metadata_passthrough: None,
},
ResponseItem::ImageGenerationCall {
id: Some("ig_123".to_string()),
id: Some(ResponseItemId::with_suffix("ig", "123")),
status: "completed".to_string(),
revised_prompt: Some("lobster".to_string()),
result: String::new(),
Expand Down Expand Up @@ -1642,15 +1643,15 @@ fn normalize_adds_missing_output_for_function_call_inserts_output() {
fn for_prompt_assigns_stable_id_to_synthetic_output_without_reordering_history() {
let items = vec![
ResponseItem::FunctionCall {
id: Some("fc_existing".to_string()),
id: Some(ResponseItemId::with_suffix("fc", "existing")),
name: "do_it".to_string(),
namespace: None,
arguments: "{}".to_string(),
call_id: "call-x".to_string(),
internal_chat_message_metadata_passthrough: None,
},
ResponseItem::Message {
id: Some("msg_later".to_string()),
id: Some(ResponseItemId::with_suffix("msg", "later")),
role: "user".to_string(),
content: vec![ContentItem::InputText {
text: "later turn".to_string(),
Expand Down
9 changes: 5 additions & 4 deletions codex-rs/core/src/context_manager/normalize.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use codex_protocol::ResponseItemId;
use codex_protocol::models::ContentItem;
use codex_protocol::models::FunctionCallOutputContentItem;
use codex_protocol::models::FunctionCallOutputPayload;
Expand Down Expand Up @@ -132,12 +133,12 @@ pub(crate) fn ensure_call_outputs_present(items: &mut Vec<ResponseItem>) {
/// outputs, so the namespace and name format must remain stable across retries
/// and resumes to preserve prompt-cache reuse. Returning `None` when the source
/// call has no ID preserves the legacy behavior for older history items.
fn synthetic_output_id(prefix: &str, item_id: Option<&str>) -> Option<String> {
fn synthetic_output_id(prefix: &str, item_id: Option<&str>) -> Option<ResponseItemId> {
let source_id = item_id.filter(|id| !id.is_empty())?;
let name = format!("{prefix}:{source_id}");
Some(format!(
"{prefix}_{}",
Uuid::new_v5(&SYNTHETIC_OUTPUT_ID_NAMESPACE, name.as_bytes())
Some(ResponseItemId::with_suffix(
prefix,
Uuid::new_v5(&SYNTHETIC_OUTPUT_ID_NAMESPACE, name.as_bytes()),
))
}

Expand Down
Loading
Loading