Add opt-in output token replay for OpenAI requests (#52742)

## What changed

- Add the disabled-by-default `output_token_replay` feature to request `output.encrypted_content` from OpenAI providers.
- Preserve encrypted message and tool-call output, message and function-call status, and output-text `annotations` and `logprobs` through history and replay. Update the generated protocol schemas and SDK types.
- Strip output ciphertext from requests to other providers while retaining it in stored history, including after replay is disabled. Remove encrypted content from memory extraction inputs.

## Testing

Add integration coverage for the feature/provider combinations, metadata replay across follow-up requests, and history preservation when resuming with replay disabled.

GitOrigin-RevId: 77729a021b6771933402aebe74ece8eb39c057e2
This commit is contained in:
pakrym-oai
2026-10-10 01:25:00 +00:00
committed by copyberry
parent e4490eff65
commit 5ef96ab278
119 changed files with 1602 additions and 91 deletions
@@ -733,6 +733,20 @@
}, },
{ {
"properties": { "properties": {
"annotations": {
"items": true,
"type": [
"array",
"null"
]
},
"logprobs": {
"items": true,
"type": [
"array",
"null"
]
},
"text": { "text": {
"type": "string" "type": "string"
}, },
@@ -2988,6 +3002,12 @@
}, },
"type": "array" "type": "array"
}, },
"encrypted_content": {
"type": [
"string",
"null"
]
},
"id": { "id": {
"type": [ "type": [
"string", "string",
@@ -3017,6 +3037,12 @@
"role": { "role": {
"type": "string" "type": "string"
}, },
"status": {
"type": [
"string",
"null"
]
},
"type": { "type": {
"enum": [ "enum": [
"message" "message"
@@ -3147,6 +3173,12 @@
"null" "null"
] ]
}, },
"encrypted_content": {
"type": [
"string",
"null"
]
},
"id": { "id": {
"description": "Legacy id field retained for compatibility with older payloads.", "description": "Legacy id field retained for compatibility with older payloads.",
"type": [ "type": [
@@ -3191,6 +3223,12 @@
"call_id": { "call_id": {
"type": "string" "type": "string"
}, },
"encrypted_content": {
"type": [
"string",
"null"
]
},
"encrypted_function_args": { "encrypted_function_args": {
"items": { "items": {
"type": "string" "type": "string"
@@ -3225,6 +3263,12 @@
"null" "null"
] ]
}, },
"status": {
"type": [
"string",
"null"
]
},
"type": { "type": {
"enum": [ "enum": [
"function_call" "function_call"
@@ -3251,6 +3295,12 @@
"null" "null"
] ]
}, },
"encrypted_content": {
"type": [
"string",
"null"
]
},
"execution": { "execution": {
"type": "string" "type": "string"
}, },
@@ -3351,6 +3401,12 @@
"call_id": { "call_id": {
"type": "string" "type": "string"
}, },
"encrypted_content": {
"type": [
"string",
"null"
]
},
"id": { "id": {
"type": [ "type": [
"string", "string",
@@ -3511,6 +3567,12 @@
} }
] ]
}, },
"encrypted_content": {
"type": [
"string",
"null"
]
},
"id": { "id": {
"type": [ "type": [
"string", "string",
@@ -3549,6 +3611,12 @@
}, },
{ {
"properties": { "properties": {
"encrypted_content": {
"type": [
"string",
"null"
]
},
"id": { "id": {
"type": [ "type": [
"string", "string",
@@ -10746,6 +10746,20 @@
}, },
{ {
"properties": { "properties": {
"annotations": {
"items": true,
"type": [
"array",
"null"
]
},
"logprobs": {
"items": true,
"type": [
"array",
"null"
]
},
"text": { "text": {
"type": "string" "type": "string"
}, },
@@ -18845,6 +18859,12 @@
}, },
"type": "array" "type": "array"
}, },
"encrypted_content": {
"type": [
"string",
"null"
]
},
"id": { "id": {
"type": [ "type": [
"string", "string",
@@ -18874,6 +18894,12 @@
"role": { "role": {
"type": "string" "type": "string"
}, },
"status": {
"type": [
"string",
"null"
]
},
"type": { "type": {
"enum": [ "enum": [
"message" "message"
@@ -19004,6 +19030,12 @@
"null" "null"
] ]
}, },
"encrypted_content": {
"type": [
"string",
"null"
]
},
"id": { "id": {
"description": "Legacy id field retained for compatibility with older payloads.", "description": "Legacy id field retained for compatibility with older payloads.",
"type": [ "type": [
@@ -19048,6 +19080,12 @@
"call_id": { "call_id": {
"type": "string" "type": "string"
}, },
"encrypted_content": {
"type": [
"string",
"null"
]
},
"encrypted_function_args": { "encrypted_function_args": {
"items": { "items": {
"type": "string" "type": "string"
@@ -19082,6 +19120,12 @@
"null" "null"
] ]
}, },
"status": {
"type": [
"string",
"null"
]
},
"type": { "type": {
"enum": [ "enum": [
"function_call" "function_call"
@@ -19108,6 +19152,12 @@
"null" "null"
] ]
}, },
"encrypted_content": {
"type": [
"string",
"null"
]
},
"execution": { "execution": {
"type": "string" "type": "string"
}, },
@@ -19208,6 +19258,12 @@
"call_id": { "call_id": {
"type": "string" "type": "string"
}, },
"encrypted_content": {
"type": [
"string",
"null"
]
},
"id": { "id": {
"type": [ "type": [
"string", "string",
@@ -19368,6 +19424,12 @@
} }
] ]
}, },
"encrypted_content": {
"type": [
"string",
"null"
]
},
"id": { "id": {
"type": [ "type": [
"string", "string",
@@ -19406,6 +19468,12 @@
}, },
{ {
"properties": { "properties": {
"encrypted_content": {
"type": [
"string",
"null"
]
},
"id": { "id": {
"type": [ "type": [
"string", "string",
@@ -6562,6 +6562,20 @@
}, },
{ {
"properties": { "properties": {
"annotations": {
"items": true,
"type": [
"array",
"null"
]
},
"logprobs": {
"items": true,
"type": [
"array",
"null"
]
},
"text": { "text": {
"type": "string" "type": "string"
}, },
@@ -14837,6 +14851,12 @@
}, },
"type": "array" "type": "array"
}, },
"encrypted_content": {
"type": [
"string",
"null"
]
},
"id": { "id": {
"type": [ "type": [
"string", "string",
@@ -14866,6 +14886,12 @@
"role": { "role": {
"type": "string" "type": "string"
}, },
"status": {
"type": [
"string",
"null"
]
},
"type": { "type": {
"enum": [ "enum": [
"message" "message"
@@ -14996,6 +15022,12 @@
"null" "null"
] ]
}, },
"encrypted_content": {
"type": [
"string",
"null"
]
},
"id": { "id": {
"description": "Legacy id field retained for compatibility with older payloads.", "description": "Legacy id field retained for compatibility with older payloads.",
"type": [ "type": [
@@ -15040,6 +15072,12 @@
"call_id": { "call_id": {
"type": "string" "type": "string"
}, },
"encrypted_content": {
"type": [
"string",
"null"
]
},
"encrypted_function_args": { "encrypted_function_args": {
"items": { "items": {
"type": "string" "type": "string"
@@ -15074,6 +15112,12 @@
"null" "null"
] ]
}, },
"status": {
"type": [
"string",
"null"
]
},
"type": { "type": {
"enum": [ "enum": [
"function_call" "function_call"
@@ -15100,6 +15144,12 @@
"null" "null"
] ]
}, },
"encrypted_content": {
"type": [
"string",
"null"
]
},
"execution": { "execution": {
"type": "string" "type": "string"
}, },
@@ -15200,6 +15250,12 @@
"call_id": { "call_id": {
"type": "string" "type": "string"
}, },
"encrypted_content": {
"type": [
"string",
"null"
]
},
"id": { "id": {
"type": [ "type": [
"string", "string",
@@ -15360,6 +15416,12 @@
} }
] ]
}, },
"encrypted_content": {
"type": [
"string",
"null"
]
},
"id": { "id": {
"type": [ "type": [
"string", "string",
@@ -15398,6 +15460,12 @@
}, },
{ {
"properties": { "properties": {
"encrypted_content": {
"type": [
"string",
"null"
]
},
"id": { "id": {
"type": [ "type": [
"string", "string",
@@ -153,6 +153,20 @@
}, },
{ {
"properties": { "properties": {
"annotations": {
"items": true,
"type": [
"array",
"null"
]
},
"logprobs": {
"items": true,
"type": [
"array",
"null"
]
},
"text": { "text": {
"type": "string" "type": "string"
}, },
@@ -528,6 +542,12 @@
}, },
"type": "array" "type": "array"
}, },
"encrypted_content": {
"type": [
"string",
"null"
]
},
"id": { "id": {
"type": [ "type": [
"string", "string",
@@ -557,6 +577,12 @@
"role": { "role": {
"type": "string" "type": "string"
}, },
"status": {
"type": [
"string",
"null"
]
},
"type": { "type": {
"enum": [ "enum": [
"message" "message"
@@ -687,6 +713,12 @@
"null" "null"
] ]
}, },
"encrypted_content": {
"type": [
"string",
"null"
]
},
"id": { "id": {
"description": "Legacy id field retained for compatibility with older payloads.", "description": "Legacy id field retained for compatibility with older payloads.",
"type": [ "type": [
@@ -731,6 +763,12 @@
"call_id": { "call_id": {
"type": "string" "type": "string"
}, },
"encrypted_content": {
"type": [
"string",
"null"
]
},
"encrypted_function_args": { "encrypted_function_args": {
"items": { "items": {
"type": "string" "type": "string"
@@ -765,6 +803,12 @@
"null" "null"
] ]
}, },
"status": {
"type": [
"string",
"null"
]
},
"type": { "type": {
"enum": [ "enum": [
"function_call" "function_call"
@@ -791,6 +835,12 @@
"null" "null"
] ]
}, },
"encrypted_content": {
"type": [
"string",
"null"
]
},
"execution": { "execution": {
"type": "string" "type": "string"
}, },
@@ -891,6 +941,12 @@
"call_id": { "call_id": {
"type": "string" "type": "string"
}, },
"encrypted_content": {
"type": [
"string",
"null"
]
},
"id": { "id": {
"type": [ "type": [
"string", "string",
@@ -1051,6 +1107,12 @@
} }
] ]
}, },
"encrypted_content": {
"type": [
"string",
"null"
]
},
"id": { "id": {
"type": [ "type": [
"string", "string",
@@ -1089,6 +1151,12 @@
}, },
{ {
"properties": { "properties": {
"encrypted_content": {
"type": [
"string",
"null"
]
},
"id": { "id": {
"type": [ "type": [
"string", "string",
@@ -215,6 +215,20 @@
}, },
{ {
"properties": { "properties": {
"annotations": {
"items": true,
"type": [
"array",
"null"
]
},
"logprobs": {
"items": true,
"type": [
"array",
"null"
]
},
"text": { "text": {
"type": "string" "type": "string"
}, },
@@ -599,6 +613,12 @@
}, },
"type": "array" "type": "array"
}, },
"encrypted_content": {
"type": [
"string",
"null"
]
},
"id": { "id": {
"type": [ "type": [
"string", "string",
@@ -628,6 +648,12 @@
"role": { "role": {
"type": "string" "type": "string"
}, },
"status": {
"type": [
"string",
"null"
]
},
"type": { "type": {
"enum": [ "enum": [
"message" "message"
@@ -758,6 +784,12 @@
"null" "null"
] ]
}, },
"encrypted_content": {
"type": [
"string",
"null"
]
},
"id": { "id": {
"description": "Legacy id field retained for compatibility with older payloads.", "description": "Legacy id field retained for compatibility with older payloads.",
"type": [ "type": [
@@ -802,6 +834,12 @@
"call_id": { "call_id": {
"type": "string" "type": "string"
}, },
"encrypted_content": {
"type": [
"string",
"null"
]
},
"encrypted_function_args": { "encrypted_function_args": {
"items": { "items": {
"type": "string" "type": "string"
@@ -836,6 +874,12 @@
"null" "null"
] ]
}, },
"status": {
"type": [
"string",
"null"
]
},
"type": { "type": {
"enum": [ "enum": [
"function_call" "function_call"
@@ -862,6 +906,12 @@
"null" "null"
] ]
}, },
"encrypted_content": {
"type": [
"string",
"null"
]
},
"execution": { "execution": {
"type": "string" "type": "string"
}, },
@@ -962,6 +1012,12 @@
"call_id": { "call_id": {
"type": "string" "type": "string"
}, },
"encrypted_content": {
"type": [
"string",
"null"
]
},
"id": { "id": {
"type": [ "type": [
"string", "string",
@@ -1122,6 +1178,12 @@
} }
] ]
}, },
"encrypted_content": {
"type": [
"string",
"null"
]
},
"id": { "id": {
"type": [ "type": [
"string", "string",
@@ -1160,6 +1222,12 @@
}, },
{ {
"properties": { "properties": {
"encrypted_content": {
"type": [
"string",
"null"
]
},
"id": { "id": {
"type": [ "type": [
"string", "string",
@@ -3,4 +3,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ImageDetail } from "./ImageDetail"; import type { ImageDetail } from "./ImageDetail";
export type ContentItem = { "type": "input_text", text: string, } | { "type": "input_image", detail?: ImageDetail, } & ({ image_url: string, } | { file_id: string, }) | { "type": "input_audio", audio_url: string, } | { "type": "output_text", text: string, }; export type ContentItem = { "type": "input_text", text: string, } | { "type": "input_image", detail?: ImageDetail, } & ({ image_url: string, } | { file_id: string, }) | { "type": "input_audio", audio_url: string, } | { "type": "output_text", text: string, annotations?: unknown[], logprobs?: unknown[], };
@@ -14,7 +14,7 @@ import type { ReasoningItemReasoningSummary } from "./ReasoningItemReasoningSumm
import type { ResponseItemId } from "./ResponseItemId"; import type { ResponseItemId } from "./ResponseItemId";
import type { WebSearchAction } from "./WebSearchAction"; import type { WebSearchAction } from "./WebSearchAction";
export type ResponseItem = { "type": "additional_tools", id?: ResponseItemId, role: string, tools: unknown[], } | { "type": "message", id?: ResponseItemId, role: string, content: Array<ContentItem>, phase?: MessagePhase, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "agent_message", id?: ResponseItemId, author: string, recipient: string, content: Array<AgentMessageInputContent>, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "reasoning", id?: ResponseItemId, summary: Array<ReasoningItemReasoningSummary>, content?: Array<ReasoningItemContent>, encrypted_content: string | null, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "local_shell_call", export type ResponseItem = { "type": "additional_tools", id?: ResponseItemId, role: string, tools: unknown[], } | { "type": "message", encrypted_content?: string, status?: string, id?: ResponseItemId, role: string, content: Array<ContentItem>, phase?: MessagePhase, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "agent_message", id?: ResponseItemId, author: string, recipient: string, content: Array<AgentMessageInputContent>, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "reasoning", id?: ResponseItemId, summary: Array<ReasoningItemReasoningSummary>, content?: Array<ReasoningItemContent>, encrypted_content: string | null, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "local_shell_call", encrypted_content?: string,
/** /**
* Legacy id field retained for compatibility with older payloads. * Legacy id field retained for compatibility with older payloads.
*/ */
@@ -22,4 +22,4 @@ id?: ResponseItemId,
/** /**
* Set when using the Responses API. * Set when using the Responses API.
*/ */
call_id: string | null, status: LocalShellStatus, action: LocalShellAction, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "function_call", id?: ResponseItemId, name: string, namespace?: string, arguments: string, encrypted_function_args?: Array<string>, call_id: string, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "tool_search_call", id?: ResponseItemId, call_id: string | null, status?: string, execution: string, arguments: unknown, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "function_call_output", id?: ResponseItemId, call_id?: string, name?: string, namespace?: string, output: FunctionCallOutputBody, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "custom_tool_call", id?: ResponseItemId, status?: string, call_id: string, name: string, namespace?: string, input: string, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "custom_tool_call_output", id?: ResponseItemId, call_id: string, name?: string, output: FunctionCallOutputBody, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "tool_search_output", id?: ResponseItemId, call_id: string | null, status: string, execution: string, tools: unknown[], internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "web_search_call", id?: ResponseItemId, status?: string, action?: WebSearchAction, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "image_generation_call", id?: ResponseItemId, status: string, revised_prompt?: string, result: string, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "compaction", id?: ResponseItemId, encrypted_content: string, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "configuration_update", reasoning: ConfigurationReasoning, } | { "type": "compaction_trigger", } | { "type": "context_compaction", id?: ResponseItemId, encrypted_content?: string, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "other" }; call_id: string | null, status: LocalShellStatus, action: LocalShellAction, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "function_call", encrypted_content?: string, status?: string, id?: ResponseItemId, name: string, namespace?: string, arguments: string, encrypted_function_args?: Array<string>, call_id: string, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "tool_search_call", encrypted_content?: string, id?: ResponseItemId, call_id: string | null, status?: string, execution: string, arguments: unknown, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "function_call_output", id?: ResponseItemId, call_id?: string, name?: string, namespace?: string, output: FunctionCallOutputBody, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "custom_tool_call", encrypted_content?: string, id?: ResponseItemId, status?: string, call_id: string, name: string, namespace?: string, input: string, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "custom_tool_call_output", id?: ResponseItemId, call_id: string, name?: string, output: FunctionCallOutputBody, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "tool_search_output", id?: ResponseItemId, call_id: string | null, status: string, execution: string, tools: unknown[], internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "web_search_call", encrypted_content?: string, id?: ResponseItemId, status?: string, action?: WebSearchAction, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "image_generation_call", encrypted_content?: string, id?: ResponseItemId, status: string, revised_prompt?: string, result: string, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "compaction", id?: ResponseItemId, encrypted_content: string, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "configuration_update", reasoning: ConfigurationReasoning, } | { "type": "compaction_trigger", } | { "type": "context_compaction", id?: ResponseItemId, encrypted_content?: string, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "other" };
@@ -5233,6 +5233,8 @@ mod tests {
})), })),
RolloutItem::ResponseItem( RolloutItem::ResponseItem(
codex_protocol::models::ResponseItem::Message { codex_protocol::models::ResponseItem::Message {
status: None,
encrypted_content: None,
id: Some(codex_protocol::ResponseItemId::with_suffix("msg", "1")), id: Some(codex_protocol::ResponseItemId::with_suffix("msg", "1")),
role: "user".into(), role: "user".into(),
content: vec![codex_protocol::models::ContentItem::InputText { content: vec![codex_protocol::models::ContentItem::InputText {
@@ -615,6 +615,8 @@ async fn metadata_and_mcp_requests_complete_while_unrelated_resume_loads_config(
thread_id: "00000000-0000-4000-8000-000000000000".to_string(), thread_id: "00000000-0000-4000-8000-000000000000".to_string(),
cwd: Some(resume_cwd.path().to_string_lossy().into_owned()), cwd: Some(resume_cwd.path().to_string_lossy().into_owned()),
history: Some(vec![ResponseItem::Message { history: Some(vec![ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -1838,6 +1838,8 @@ async fn assert_thread_fork_freezes_active_paginated_turn_as_interrupted(
let user_response_item = |id: &str| { let user_response_item = |id: &str| {
RolloutItem::ResponseItem( RolloutItem::ResponseItem(
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -87,15 +87,21 @@ async fn thread_inject_items_adds_raw_response_items_to_thread_history(
let injected_text = "Injected assistant context"; let injected_text = "Injected assistant context";
let injected_item = ResponseItem::Message { let injected_item = ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "assistant".to_string(), role: "assistant".to_string(),
content: vec![ContentItem::OutputText { content: vec![ContentItem::OutputText {
annotations: None,
logprobs: None,
text: injected_text.to_string(), text: injected_text.to_string(),
}], }],
phase: None, phase: None,
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}; };
let developer_item = |text: &str| ResponseItem::Message { let developer_item = |text: &str| ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "developer".to_string(), role: "developer".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -561,6 +567,8 @@ async fn thread_inject_items_adds_raw_response_items_after_a_turn() -> Result<()
.await??; .await??;
let injected_item = ResponseItem::Message { let injected_item = ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "developer".to_string(), role: "developer".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -4972,6 +4972,8 @@ async fn thread_resume_rejects_history_when_thread_is_running() -> Result<()> {
.send_thread_resume_request(ThreadResumeParams { .send_thread_resume_request(ThreadResumeParams {
thread_id: thread_id.clone(), thread_id: thread_id.clone(),
history: Some(vec![ResponseItem::Message { history: Some(vec![ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -6101,6 +6103,8 @@ async fn thread_resume_supports_history_and_overrides() -> Result<()> {
let history_text = "Hello from history"; let history_text = "Hello from history";
let history = vec![ResponseItem::Message { let history = vec![ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -946,6 +946,8 @@ mod tests {
let api_request = ResponsesApiRequest { let api_request = ResponsesApiRequest {
model: "gpt-test".to_string(), model: "gpt-test".to_string(),
input: vec![ResponseItem::Message { input: vec![ResponseItem::Message {
status: None,
encrypted_content: None,
id: Some(ResponseItemId::with_suffix("msg", "1")), id: Some(ResponseItemId::with_suffix("msg", "1")),
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -157,6 +157,8 @@ mod tests {
model: "gpt-test".to_string(), model: "gpt-test".to_string(),
reasoning: None, reasoning: None,
input: Some(SearchInput::Items(vec![ResponseItem::Message { input: Some(SearchInput::Items(vec![ResponseItem::Message {
status: None,
encrypted_content: None,
id: Some(ResponseItemId::with_suffix("msg", "search")), id: Some(ResponseItemId::with_suffix("msg", "search")),
role: "user".to_string(), role: "user".to_string(),
content: vec![ content: vec![
+2
View File
@@ -770,6 +770,8 @@ mod tests {
] if start_content.is_empty() ] if start_content.is_empty()
&& text == "First result." && text == "First result."
&& done_content == &vec![codex_protocol::models::ContentItem::OutputText { && done_content == &vec![codex_protocol::models::ContentItem::OutputText {
annotations: None,
logprobs: None,
text: "First result.".to_string() text: "First result.".to_string()
}]); }]);
} }
+6
View File
@@ -340,6 +340,8 @@ async fn responses_client_stream_request_preserves_item_ids() -> Result<()> {
let request = ResponsesApiRequest { let request = ResponsesApiRequest {
model: "gpt-test".into(), model: "gpt-test".into(),
input: vec![ResponseItem::Message { input: vec![ResponseItem::Message {
status: None,
encrypted_content: None,
id: Some(ResponseItemId::with_suffix("msg", "1")), id: Some(ResponseItemId::with_suffix("msg", "1")),
role: "user".into(), role: "user".into(),
content: vec![ContentItem::InputText { text: "hi".into() }], content: vec![ContentItem::InputText { text: "hi".into() }],
@@ -394,6 +396,8 @@ async fn responses_client_stream_request_sends_routing_fields_ahead_of_large_inp
let request = ResponsesApiRequest { let request = ResponsesApiRequest {
model: "gpt-test".into(), model: "gpt-test".into(),
input: vec![ResponseItem::Message { input: vec![ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".into(), role: "user".into(),
content: vec![ContentItem::InputText { text: large_input }], content: vec![ContentItem::InputText { text: large_input }],
@@ -604,6 +608,8 @@ async fn azure_store_sends_ids_and_headers() -> Result<()> {
let request = ResponsesApiRequest { let request = ResponsesApiRequest {
model: "gpt-test".into(), model: "gpt-test".into(),
input: vec![ResponseItem::Message { input: vec![ResponseItem::Message {
status: None,
encrypted_content: None,
id: Some(ResponseItemId::with_suffix("msg", "1")), id: Some(ResponseItemId::with_suffix("msg", "1")),
role: "user".into(), role: "user".into(),
content: vec![ContentItem::InputText { text: "hi".into() }], content: vec![ContentItem::InputText { text: "hi".into() }],
@@ -38,6 +38,8 @@ impl From<RenderedFragment> for ResponseItem {
let (content, content_kind) = annotated_content.into_parts(); let (content, content_kind) = annotated_content.into_parts();
Self::Message { Self::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: role.to_string(), role: role.to_string(),
content: vec![content], content: vec![content],
+6
View File
@@ -1052,6 +1052,9 @@
"omit_app_server_notification_media": { "omit_app_server_notification_media": {
"type": "boolean" "type": "boolean"
}, },
"output_token_replay": {
"type": "boolean"
},
"personality": { "personality": {
"type": "boolean" "type": "boolean"
}, },
@@ -7352,6 +7355,9 @@
"omit_app_server_notification_media": { "omit_app_server_notification_media": {
"type": "boolean" "type": "boolean"
}, },
"output_token_replay": {
"type": "boolean"
},
"personality": { "personality": {
"type": "boolean" "type": "boolean"
}, },
+39 -3
View File
@@ -196,6 +196,8 @@ fn rollout_response_item(item: ResponseItem) -> RolloutItem {
fn user_message(text: &str) -> ResponseItem { fn user_message(text: &str) -> ResponseItem {
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -208,9 +210,13 @@ fn user_message(text: &str) -> ResponseItem {
fn assistant_message(text: &str, phase: Option<MessagePhase>) -> ResponseItem { fn assistant_message(text: &str, phase: Option<MessagePhase>) -> ResponseItem {
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "assistant".to_string(), role: "assistant".to_string(),
content: vec![ContentItem::OutputText { content: vec![ContentItem::OutputText {
annotations: None,
logprobs: None,
text: text.to_string(), text: text.to_string(),
}], }],
phase, phase,
@@ -237,6 +243,8 @@ fn register_session_root_skips_threads_with_explicit_parent() {
fn spawn_agent_call(call_id: &str) -> ResponseItem { fn spawn_agent_call(call_id: &str) -> ResponseItem {
ResponseItem::FunctionCall { ResponseItem::FunctionCall {
status: None,
encrypted_content: None,
id: None, id: None,
name: "spawn_agent".to_string(), name: "spawn_agent".to_string(),
namespace: None, namespace: None,
@@ -354,7 +362,7 @@ fn has_subagent_notification<'a>(
return false; return false;
} }
content.iter().any(|content_item| match content_item { content.iter().any(|content_item| match content_item {
ContentItem::InputText { text } | ContentItem::OutputText { text } => { ContentItem::InputText { text } | ContentItem::OutputText { text, .. } => {
SubagentNotification::matches_text(text) SubagentNotification::matches_text(text)
} }
ContentItem::InputImage { .. } | ContentItem::InputAudio { .. } => false, ContentItem::InputImage { .. } | ContentItem::InputAudio { .. } => false,
@@ -372,7 +380,7 @@ fn history_contains_text<'a>(
return false; return false;
}; };
content.iter().any(|content_item| match content_item { content.iter().any(|content_item| match content_item {
ContentItem::InputText { text } | ContentItem::OutputText { text } => { ContentItem::InputText { text } | ContentItem::OutputText { text, .. } => {
text.contains(needle) text.contains(needle)
} }
ContentItem::InputImage { .. } | ContentItem::InputAudio { .. } => false, ContentItem::InputImage { .. } | ContentItem::InputAudio { .. } => false,
@@ -415,7 +423,7 @@ fn history_contains_assistant_inter_agent_communication<'a>(
return false; return false;
} }
content.iter().any(|content_item| match content_item { content.iter().any(|content_item| match content_item {
ContentItem::OutputText { text } => { ContentItem::OutputText { text, .. } => {
serde_json::from_str::<InterAgentCommunication>(text) serde_json::from_str::<InterAgentCommunication>(text)
.ok() .ok()
.as_ref() .as_ref()
@@ -2307,6 +2315,8 @@ async fn spawn_agent_fork_from_paginated_parent_uses_model_context_prefix(
.session .session
.persist_rollout_items(&[ .persist_rollout_items(&[
rollout_response_item(ResponseItem::Message { rollout_response_item(ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "developer".to_string(), role: "developer".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -2816,6 +2826,8 @@ async fn spawn_agent_can_fork_parent_thread_history_with_sanitized_items() {
turn_context.as_ref(), turn_context.model_info(), turn_context.as_ref(), turn_context.model_info(),
&[ &[
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "developer".to_string(), role: "developer".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -2825,6 +2837,8 @@ async fn spawn_agent_can_fork_parent_thread_history_with_sanitized_items() {
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}, },
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "developer".to_string(), role: "developer".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -2834,6 +2848,8 @@ async fn spawn_agent_can_fork_parent_thread_history_with_sanitized_items() {
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}, },
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "developer".to_string(), role: "developer".to_string(),
content: vec![ content: vec![
@@ -2987,6 +3003,8 @@ async fn spawn_agent_can_fork_parent_thread_history_with_sanitized_items() {
.cloned() .cloned()
.expect("parent final answer should be recorded"); .expect("parent final answer should be recorded");
let mut expected_developer_message = ResponseItem::Message { let mut expected_developer_message = ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "developer".to_string(), role: "developer".to_string(),
content: vec![ content: vec![
@@ -3196,6 +3214,8 @@ async fn spawn_agent_fork_strips_parent_usage_hints_from_compacted_history(marke
tool_declarations.clone(), tool_declarations.clone(),
ContextualUserFragment::into(crate::context::GuardianApprovedAction::new("parent-private-release".to_owned())), ContextualUserFragment::into(crate::context::GuardianApprovedAction::new("parent-private-release".to_owned())),
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -3207,6 +3227,8 @@ async fn spawn_agent_fork_strips_parent_usage_hints_from_compacted_history(marke
ContextualUserFragment::into(catalog_role("Catalog parent root guidance.")), ContextualUserFragment::into(catalog_role("Catalog parent root guidance.")),
parent_task.to_model_input_item(), parent_task.to_model_input_item(),
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "developer".to_string(), role: "developer".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -3221,6 +3243,8 @@ async fn spawn_agent_fork_strips_parent_usage_hints_from_compacted_history(marke
), ),
}, },
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "developer".to_string(), role: "developer".to_string(),
content: vec![ content: vec![
@@ -3451,6 +3475,8 @@ async fn spawn_agent_full_fork_restores_instructions_after_compaction_discards_p
let parent_spawn_call_id = "spawn-call-compacted-stale-instructions".to_string(); let parent_spawn_call_id = "spawn-call-compacted-stale-instructions".to_string();
let replacement_history = vec![ let replacement_history = vec![
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -3460,6 +3486,8 @@ async fn spawn_agent_full_fork_restores_instructions_after_compaction_discards_p
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}, },
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "developer".to_string(), role: "developer".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -3483,6 +3511,8 @@ async fn spawn_agent_full_fork_restores_instructions_after_compaction_discards_p
.session .session
.persist_rollout_items(&[ .persist_rollout_items(&[
rollout_response_item(ResponseItem::Message { rollout_response_item(ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "developer".to_string(), role: "developer".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -3636,6 +3666,8 @@ async fn spawn_agent_full_fork_legacy_compaction_rebuilds_child_instructions_onc
None => "spawn-call-legacy-compact-without-parent", None => "spawn-call-legacy-compact-without-parent",
}; };
let parent_user_message = ResponseItem::Message { let parent_user_message = ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -3683,6 +3715,8 @@ async fn spawn_agent_full_fork_legacy_compaction_rebuilds_child_instructions_onc
]; ];
if let Some(instructions) = parent_developer_instructions { if let Some(instructions) = parent_developer_instructions {
rollout_items.push(rollout_response_item(ResponseItem::Message { rollout_items.push(rollout_response_item(ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "developer".to_string(), role: "developer".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -3693,6 +3727,8 @@ async fn spawn_agent_full_fork_legacy_compaction_rebuilds_child_instructions_onc
})); }));
} }
rollout_items.push(rollout_response_item(ResponseItem::Message { rollout_items.push(rollout_response_item(ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "developer".to_string(), role: "developer".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
+15 -1
View File
@@ -213,6 +213,7 @@ struct ModelClientState {
include_timing_metrics: bool, include_timing_metrics: bool,
beta_features_header: Option<String>, beta_features_header: Option<String>,
concurrent_reasoning_summaries_enabled: bool, concurrent_reasoning_summaries_enabled: bool,
output_token_replay_enabled: bool,
include_attestation: bool, include_attestation: bool,
attestation_provider: Option<Arc<dyn AttestationProvider>>, attestation_provider: Option<Arc<dyn AttestationProvider>>,
disable_websockets: AtomicBool, disable_websockets: AtomicBool,
@@ -547,6 +548,7 @@ impl ModelClient {
include_timing_metrics: bool, include_timing_metrics: bool,
beta_features_header: Option<String>, beta_features_header: Option<String>,
concurrent_reasoning_summaries_enabled: bool, concurrent_reasoning_summaries_enabled: bool,
output_token_replay_enabled: bool,
attestation_provider: Option<Arc<dyn AttestationProvider>>, attestation_provider: Option<Arc<dyn AttestationProvider>>,
http_client_factory: HttpClientFactory, http_client_factory: HttpClientFactory,
workspace_routing: WorkspaceRoutingContext, workspace_routing: WorkspaceRoutingContext,
@@ -585,6 +587,7 @@ impl ModelClient {
include_timing_metrics, include_timing_metrics,
beta_features_header, beta_features_header,
concurrent_reasoning_summaries_enabled, concurrent_reasoning_summaries_enabled,
output_token_replay_enabled,
include_attestation, include_attestation,
attestation_provider, attestation_provider,
disable_websockets: AtomicBool::new(false), disable_websockets: AtomicBool::new(false),
@@ -999,6 +1002,14 @@ impl ModelClient {
if !is_openai { if !is_openai {
for item in &mut input { for item in &mut input {
item.clear_internal_chat_message_metadata_passthrough(); item.clear_internal_chat_message_metadata_passthrough();
if !matches!(
item,
ResponseItem::Reasoning { .. }
| ResponseItem::Compaction { .. }
| ResponseItem::ContextCompaction { .. }
) {
item.clear_encrypted_content();
}
if let ResponseItem::FunctionCall { if let ResponseItem::FunctionCall {
encrypted_function_args, encrypted_function_args,
.. ..
@@ -1015,7 +1026,10 @@ impl ModelClient {
.then_some(StreamOptions { .then_some(StreamOptions {
reasoning_summary_delivery: codex_api::ReasoningSummaryDelivery::SequentialCutoff, reasoning_summary_delivery: codex_api::ReasoningSummaryDelivery::SequentialCutoff,
}); });
let include = vec!["reasoning.encrypted_content".to_string()]; let mut include = vec!["reasoning.encrypted_content".to_string()];
if self.state.output_token_replay_enabled && is_openai {
include.push("output.encrypted_content".to_string());
}
let verbosity = if model_info.support_verbosity { let verbosity = if model_info.support_verbosity {
self.state.model_verbosity.or(model_info.default_verbosity) self.state.model_verbosity.or(model_info.default_verbosity)
} else { } else {
+2
View File
@@ -21,6 +21,8 @@ fn prompt_with_image_outputs(detail: Option<ImageDetail>) -> Prompt {
Prompt { Prompt {
input: vec![ input: vec![
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ content: vec![
+6
View File
@@ -130,6 +130,7 @@ fn test_model_client_with_thread_id(
/*include_timing_metrics*/ false, /*include_timing_metrics*/ false,
/*beta_features_header*/ None, /*beta_features_header*/ None,
/*concurrent_reasoning_summaries_enabled*/ false, /*concurrent_reasoning_summaries_enabled*/ false,
/*output_token_replay_enabled*/ false,
/*attestation_provider*/ None, /*attestation_provider*/ None,
HttpClientFactory::new(OutboundProxyPolicy::ReqwestDefault), HttpClientFactory::new(OutboundProxyPolicy::ReqwestDefault),
codex_model_provider::WorkspaceRoutingContext::new( codex_model_provider::WorkspaceRoutingContext::new(
@@ -1434,9 +1435,13 @@ fn started_inference_attempt(temp: &TempDir) -> anyhow::Result<InferenceTraceAtt
fn output_message(id: &str, text: &str) -> ResponseItem { fn output_message(id: &str, text: &str) -> ResponseItem {
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: Some(codex_protocol::ResponseItemId::with_suffix("msg", id)), id: Some(codex_protocol::ResponseItemId::with_suffix("msg", id)),
role: "assistant".to_string(), role: "assistant".to_string(),
content: vec![ContentItem::OutputText { content: vec![ContentItem::OutputText {
annotations: None,
logprobs: None,
text: text.to_string(), text: text.to_string(),
}], }],
phase: None, phase: None,
@@ -2020,6 +2025,7 @@ fn model_client_with_counting_attestation(
/*include_timing_metrics*/ false, /*include_timing_metrics*/ false,
/*beta_features_header*/ None, /*beta_features_header*/ None,
/*concurrent_reasoning_summaries_enabled*/ false, /*concurrent_reasoning_summaries_enabled*/ false,
/*output_token_replay_enabled*/ false,
Some(Arc::new(CountingAttestationProvider { Some(Arc::new(CountingAttestationProvider {
calls: attestation_calls.clone(), calls: attestation_calls.clone(),
})), })),
@@ -90,6 +90,7 @@ async fn forward_events_filters_private_events_before_blocked_send_is_cancelled(
} }
let visible_msg = EventMsg::RawResponseItem(RawResponseItemEvent { let visible_msg = EventMsg::RawResponseItem(RawResponseItemEvent {
item: ResponseItem::CustomToolCall { item: ResponseItem::CustomToolCall {
encrypted_content: None,
id: None, id: None,
status: None, status: None,
call_id: "call-1".to_string(), call_id: "call-1".to_string(),
+3 -1
View File
@@ -538,7 +538,7 @@ pub fn content_items_to_text(content: &[ContentItem]) -> Option<String> {
let mut pieces = Vec::new(); let mut pieces = Vec::new();
for item in content { for item in content {
match item { match item {
ContentItem::InputText { text } | ContentItem::OutputText { text } => { ContentItem::InputText { text } | ContentItem::OutputText { text, .. } => {
if !text.is_empty() { if !text.is_empty() {
pieces.push(text.as_str()); pieces.push(text.as_str());
} }
@@ -733,6 +733,8 @@ fn build_compacted_history_with_limit(
} }
selected_messages.push(ResponseItemEnvelope { selected_messages.push(ResponseItemEnvelope {
item: ResponseItem::Message { item: ResponseItem::Message {
status: None,
encrypted_content: None,
id: id.clone(), id: id.clone(),
role: "user".to_owned(), role: "user".to_owned(),
content, content,
+22 -2
View File
@@ -719,7 +719,7 @@ fn message_text_token_count(item: &ResponseItem) -> usize {
content content
.iter() .iter()
.map(|item| match item { .map(|item| match item {
ContentItem::InputText { text } | ContentItem::OutputText { text } => { ContentItem::InputText { text } | ContentItem::OutputText { text, .. } => {
approx_token_count(text) approx_token_count(text)
} }
ContentItem::InputImage { .. } | ContentItem::InputAudio { .. } => 0, ContentItem::InputImage { .. } | ContentItem::InputAudio { .. } => 0,
@@ -737,7 +737,7 @@ fn truncate_message_text_to_token_budget(
let mut truncated_content = Vec::with_capacity(content.len()); let mut truncated_content = Vec::with_capacity(content.len());
for mut content_item in content { for mut content_item in content {
match content_item.content_mut() { match content_item.content_mut() {
ContentItem::InputText { text } | ContentItem::OutputText { text } => { ContentItem::InputText { text } | ContentItem::OutputText { text, .. } => {
if remaining == 0 { if remaining == 0 {
continue; continue;
} }
@@ -783,6 +783,8 @@ mod tests {
fn message(role: &str, text: &str, phase: Option<MessagePhase>) -> ResponseItem { fn message(role: &str, text: &str, phase: Option<MessagePhase>) -> ResponseItem {
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: role.to_string(), role: role.to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -854,6 +856,8 @@ mod tests {
message("assistant", "commentary", Some(MessagePhase::Commentary)), message("assistant", "commentary", Some(MessagePhase::Commentary)),
message("assistant", "final", Some(MessagePhase::FinalAnswer)), message("assistant", "final", Some(MessagePhase::FinalAnswer)),
ResponseItem::FunctionCall { ResponseItem::FunctionCall {
status: None,
encrypted_content: None,
id: None, id: None,
name: "shell_command".to_string(), name: "shell_command".to_string(),
namespace: None, namespace: None,
@@ -990,6 +994,8 @@ mod tests {
#[test] #[test]
fn build_v2_compacted_history_counts_retained_input_images() { fn build_v2_compacted_history_counts_retained_input_images() {
let input = vec![ResponseItem::Message { let input = vec![ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ content: vec![
@@ -1039,6 +1045,8 @@ mod tests {
truncated, truncated,
vec![ vec![
ResponseItem::Message { ResponseItem::Message {
encrypted_content: None,
status: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -1060,6 +1068,8 @@ mod tests {
#[test] #[test]
fn retained_history_truncation_preserves_images_and_truncates_later_text_parts() { fn retained_history_truncation_preserves_images_and_truncates_later_text_parts() {
let item = ResponseItem::Message { let item = ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ content: vec![
@@ -1073,6 +1083,8 @@ mod tests {
detail: None, detail: None,
}, },
ContentItem::OutputText { ContentItem::OutputText {
annotations: None,
logprobs: None,
text: "uvwxyz".to_string(), text: "uvwxyz".to_string(),
}, },
ContentItem::InputText { ContentItem::InputText {
@@ -1130,6 +1142,8 @@ mod tests {
assert_eq!( assert_eq!(
truncated, truncated,
vec![ResponseItem::Message { vec![ResponseItem::Message {
encrypted_content: None,
status: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ content: vec![
@@ -1143,6 +1157,8 @@ mod tests {
detail: None, detail: None,
}, },
ContentItem::OutputText { ContentItem::OutputText {
annotations: None,
logprobs: None,
text: "uv…1 tokens truncated…yz".to_string(), text: "uv…1 tokens truncated…yz".to_string(),
}, },
ContentItem::InputImage { ContentItem::InputImage {
@@ -1172,6 +1188,8 @@ mod tests {
#[test] #[test]
fn retained_history_truncation_charges_image_only_messages() { fn retained_history_truncation_charges_image_only_messages() {
let image_only_message = ResponseItem::Message { let image_only_message = ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputImage { content: vec![ContentItem::InputImage {
@@ -1198,6 +1216,8 @@ mod tests {
#[test] #[test]
fn retained_history_truncation_drops_image_only_messages_after_budget_is_spent() { fn retained_history_truncation_drops_image_only_messages_after_budget_is_spent() {
let image_only_message = ResponseItem::Message { let image_only_message = ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputImage { content: vec![ContentItem::InputImage {
@@ -8,6 +8,8 @@ use pretty_assertions::assert_eq;
fn message(content: Vec<ContentItem>) -> ResponseItem { fn message(content: Vec<ContentItem>) -> ResponseItem {
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content, content,
@@ -14,7 +14,7 @@ use codex_utils_output_truncation::truncate_text;
pub(super) fn content_item_token_count(item: &ContentItem) -> usize { pub(super) fn content_item_token_count(item: &ContentItem) -> usize {
match item { match item {
ContentItem::InputText { text } | ContentItem::OutputText { text } => { ContentItem::InputText { text } | ContentItem::OutputText { text, .. } => {
approx_token_count(text) approx_token_count(text)
} }
ContentItem::InputImage { image, detail } => usize::try_from( ContentItem::InputImage { image, detail } => usize::try_from(
@@ -72,7 +72,7 @@ pub(super) fn truncate_message_to_token_budget(
} }
let mut item = content.pop()?; let mut item = content.pop()?;
match item.content_mut() { match item.content_mut() {
ContentItem::InputText { text } | ContentItem::OutputText { text } => { ContentItem::InputText { text } | ContentItem::OutputText { text, .. } => {
if remaining == 0 { if remaining == 0 {
continue; continue;
} }
+42
View File
@@ -53,6 +53,8 @@ async fn local_compaction_respects_tool_metadata_state(
let arguments = json!({"plan": [{"step": "x".repeat(7 * 1024), "status": "completed"}]}); let arguments = json!({"plan": [{"step": "x".repeat(7 * 1024), "status": "completed"}]});
assert!(serde_json::to_vec(&arguments)?.len() < 8 * 1024); assert!(serde_json::to_vec(&arguments)?.len() < 8 * 1024);
items.push(ResponseItem::FunctionCall { items.push(ResponseItem::FunctionCall {
status: None,
encrypted_content: None,
id: None, id: None,
name: "update_plan".to_string(), name: "update_plan".to_string(),
namespace: None, namespace: None,
@@ -222,6 +224,8 @@ fn raw(items: Vec<ResponseItemEnvelope>) -> Vec<ResponseItem> {
fn user_message(text: &str) -> ResponseItem { fn user_message(text: &str) -> ResponseItem {
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -247,9 +251,13 @@ fn content_items_to_text_joins_non_empty_segments() {
text: "hello".to_string(), text: "hello".to_string(),
}, },
ContentItem::OutputText { ContentItem::OutputText {
annotations: None,
logprobs: None,
text: String::new(), text: String::new(),
}, },
ContentItem::OutputText { ContentItem::OutputText {
annotations: None,
logprobs: None,
text: "world".to_string(), text: "world".to_string(),
}, },
]; ];
@@ -277,15 +285,21 @@ fn content_items_to_text_ignores_image_only_content() {
fn collect_user_messages_extracts_user_text_only() { fn collect_user_messages_extracts_user_text_only() {
let items = vec![ let items = vec![
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: Some(ResponseItemId::with_suffix("msg", "assistant")), id: Some(ResponseItemId::with_suffix("msg", "assistant")),
role: "assistant".to_string(), role: "assistant".to_string(),
content: vec![ContentItem::OutputText { content: vec![ContentItem::OutputText {
annotations: None,
logprobs: None,
text: "ignored".to_string(), text: "ignored".to_string(),
}], }],
phase: None, phase: None,
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}, },
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: Some(ResponseItemId::with_suffix("msg", "user")), id: Some(ResponseItemId::with_suffix("msg", "user")),
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -370,6 +384,8 @@ fn collect_annotated_user_messages_extracts_user_text_only(
fn collect_user_messages_filters_session_prefix_entries() { fn collect_user_messages_filters_session_prefix_entries() {
let items = vec![ let items = vec![
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -384,6 +400,8 @@ do things
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}, },
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -393,6 +411,8 @@ do things
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}, },
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -538,6 +558,8 @@ fn build_compacted_history_preserves_user_message_passthrough_metadata() {
vec![ vec![
ResponseItemEnvelope { ResponseItemEnvelope {
item: ResponseItem::Message { item: ResponseItem::Message {
encrypted_content: None,
status: None,
id: Some(ResponseItemId::with_suffix("msg", "user")), id: Some(ResponseItemId::with_suffix("msg", "user")),
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -579,6 +601,8 @@ fn assemble_compaction_history_keeps_prefix_first_and_summary_last() {
}; };
let compacted_history = vec![ let compacted_history = vec![
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -588,6 +612,8 @@ fn assemble_compaction_history_keeps_prefix_first_and_summary_last() {
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}, },
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -598,6 +624,8 @@ fn assemble_compaction_history_keeps_prefix_first_and_summary_last() {
}, },
agent_completion.clone(), agent_completion.clone(),
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -608,6 +636,8 @@ fn assemble_compaction_history_keeps_prefix_first_and_summary_last() {
}, },
]; ];
let initial_context = vec![ResponseItem::Message { let initial_context = vec![ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "developer".to_string(), role: "developer".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -625,6 +655,8 @@ fn assemble_compaction_history_keeps_prefix_first_and_summary_last() {
let expected = vec![ let expected = vec![
prefix, prefix,
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -634,6 +666,8 @@ fn assemble_compaction_history_keeps_prefix_first_and_summary_last() {
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}, },
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "developer".to_string(), role: "developer".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -643,6 +677,8 @@ fn assemble_compaction_history_keeps_prefix_first_and_summary_last() {
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}, },
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -653,6 +689,8 @@ fn assemble_compaction_history_keeps_prefix_first_and_summary_last() {
}, },
agent_completion, agent_completion,
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -683,6 +721,8 @@ fn assemble_compaction_history_keeps_compaction_last() {
}, },
]; ];
let initial_context = vec![ResponseItem::Message { let initial_context = vec![ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "developer".to_string(), role: "developer".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -699,6 +739,8 @@ fn assemble_compaction_history_keeps_compaction_last() {
)); ));
let expected = vec![ let expected = vec![
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "developer".to_string(), role: "developer".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -244,6 +244,8 @@ fn missing_retained_fragment_is_rendered_again() {
); );
let previous = world_state.render_full().0; let previous = world_state.render_full().0;
let retained = ResponseItem::Message { let retained = ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "developer".to_string(), role: "developer".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
+4 -2
View File
@@ -356,7 +356,9 @@ impl ContextManager {
.iter() .iter()
.filter_map(|content| match content { .filter_map(|content| match content {
ContentItem::InputText { text } ContentItem::InputText { text }
| ContentItem::OutputText { text } => Some(text.as_str()), | ContentItem::OutputText { text, .. } => {
Some(text.as_str())
}
_ => None, _ => None,
}) })
.collect::<Vec<_>>() .collect::<Vec<_>>()
@@ -1100,7 +1102,7 @@ fn estimate_response_item_model_visible_bytes(item: &ResponseItem) -> i64 {
ResponseItem::Message { content, .. } => content ResponseItem::Message { content, .. } => content
.iter() .iter()
.map(|part| match part { .map(|part| match part {
ContentItem::InputText { text } | ContentItem::OutputText { text } => { ContentItem::InputText { text } | ContentItem::OutputText { text, .. } => {
text_bytes(text) text_bytes(text)
} }
ContentItem::InputImage { image, detail } => { ContentItem::InputImage { image, detail } => {
@@ -85,9 +85,13 @@ fn pcm_wav_data_url(sample_count: u32) -> (String, usize) {
fn assistant_msg(text: &str) -> ResponseItem { fn assistant_msg(text: &str) -> ResponseItem {
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "assistant".to_string(), role: "assistant".to_string(),
content: vec![ContentItem::OutputText { content: vec![ContentItem::OutputText {
annotations: None,
logprobs: None,
text: text.to_string(), text: text.to_string(),
}], }],
phase: None, phase: None,
@@ -104,9 +108,13 @@ fn inter_agent_assistant_msg(text: &str) -> ResponseItem {
/*trigger_turn*/ true, /*trigger_turn*/ true,
); );
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "assistant".to_string(), role: "assistant".to_string(),
content: vec![ContentItem::OutputText { content: vec![ContentItem::OutputText {
annotations: None,
logprobs: None,
text: serde_json::to_string(&communication).unwrap(), text: serde_json::to_string(&communication).unwrap(),
}], }],
phase: None, phase: None,
@@ -651,9 +659,13 @@ fn world_state_reconciles_matching_legacy_history_once() {
fn user_msg(text: &str) -> ResponseItem { fn user_msg(text: &str) -> ResponseItem {
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::OutputText { content: vec![ContentItem::OutputText {
annotations: None,
logprobs: None,
text: text.to_string(), text: text.to_string(),
}], }],
phase: None, phase: None,
@@ -663,6 +675,8 @@ fn user_msg(text: &str) -> ResponseItem {
fn user_input_text_msg(text: &str) -> ResponseItem { fn user_input_text_msg(text: &str) -> ResponseItem {
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -675,6 +689,8 @@ fn user_input_text_msg(text: &str) -> ResponseItem {
fn developer_msg(text: &str) -> ResponseItem { fn developer_msg(text: &str) -> ResponseItem {
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "developer".to_string(), role: "developer".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -687,6 +703,8 @@ fn developer_msg(text: &str) -> ResponseItem {
fn developer_msg_with_fragments(texts: &[&str]) -> ResponseItem { fn developer_msg_with_fragments(texts: &[&str]) -> ResponseItem {
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "developer".to_string(), role: "developer".to_string(),
content: texts content: texts
@@ -781,9 +799,13 @@ fn filters_non_api_messages() {
let policy = TruncationPolicy::Tokens(10_000); let policy = TruncationPolicy::Tokens(10_000);
// System message is not API messages; Other is ignored. // System message is not API messages; Other is ignored.
let system = ResponseItem::Message { let system = ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "system".to_string(), role: "system".to_string(),
content: vec![ContentItem::OutputText { content: vec![ContentItem::OutputText {
annotations: None,
logprobs: None,
text: "ignored".to_string(), text: "ignored".to_string(),
}], }],
phase: None, phase: None,
@@ -828,6 +850,8 @@ fn retains_only_harness_authored_configuration_updates() {
}, },
ResponseItemEnvelope { ResponseItemEnvelope {
item: ResponseItem::Message { item: ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "system".to_string(), role: "system".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -1055,6 +1079,8 @@ fn record_annotated_items_preserves_metadata_while_processing_item(
fn for_prompt_annotated_preserves_metadata_while_normalizing_item() { fn for_prompt_annotated_preserves_metadata_while_normalizing_item() {
let envelope = ResponseItemEnvelope { let envelope = ResponseItemEnvelope {
item: ResponseItem::Message { item: ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ content: vec![
@@ -1138,6 +1164,8 @@ fn total_token_usage_includes_all_items_after_last_model_generated_item() {
fn for_prompt_strips_media_when_model_does_not_support_it() { fn for_prompt_strips_media_when_model_does_not_support_it() {
let items = vec![ let items = vec![
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ content: vec![
@@ -1171,6 +1199,8 @@ fn for_prompt_strips_media_when_model_does_not_support_it() {
), ),
}, },
ResponseItem::FunctionCall { ResponseItem::FunctionCall {
status: None,
encrypted_content: None,
id: None, id: None,
name: "view_image".to_string(), name: "view_image".to_string(),
namespace: None, namespace: None,
@@ -1201,6 +1231,7 @@ fn for_prompt_strips_media_when_model_does_not_support_it() {
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}, },
ResponseItem::CustomToolCall { ResponseItem::CustomToolCall {
encrypted_content: None,
id: None, id: None,
status: None, status: None,
call_id: "tool-1".to_string(), call_id: "tool-1".to_string(),
@@ -1237,6 +1268,8 @@ fn for_prompt_strips_media_when_model_does_not_support_it() {
let expected = vec![ let expected = vec![
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ content: vec![
@@ -1269,6 +1302,8 @@ fn for_prompt_strips_media_when_model_does_not_support_it() {
), ),
}, },
ResponseItem::FunctionCall { ResponseItem::FunctionCall {
status: None,
encrypted_content: None,
id: None, id: None,
name: "view_image".to_string(), name: "view_image".to_string(),
namespace: None, namespace: None,
@@ -1298,6 +1333,7 @@ fn for_prompt_strips_media_when_model_does_not_support_it() {
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}, },
ResponseItem::CustomToolCall { ResponseItem::CustomToolCall {
encrypted_content: None,
id: None, id: None,
status: None, status: None,
call_id: "tool-1".to_string(), call_id: "tool-1".to_string(),
@@ -1339,6 +1375,8 @@ fn for_prompt_strips_media_when_model_does_not_support_it() {
// With image support, images are preserved // With image support, images are preserved
let modalities = default_input_modalities(); let modalities = default_input_modalities();
let with_images = create_history_with_items(vec![ResponseItem::Message { let with_images = create_history_with_items(vec![ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ content: vec![
@@ -1365,6 +1403,8 @@ fn for_prompt_strips_media_when_model_does_not_support_it() {
} }
let audio_message = ResponseItem::Message { let audio_message = ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputAudio { content: vec![ContentItem::InputAudio {
@@ -1384,6 +1424,7 @@ fn for_prompt_strips_media_when_model_does_not_support_it() {
fn for_prompt_preserves_image_generation_calls_when_images_are_supported() { fn for_prompt_preserves_image_generation_calls_when_images_are_supported() {
let history = create_history_with_items(vec![ let history = create_history_with_items(vec![
ResponseItem::ImageGenerationCall { ResponseItem::ImageGenerationCall {
encrypted_content: None,
id: Some(ResponseItemId::with_suffix("ig", "123")), id: Some(ResponseItemId::with_suffix("ig", "123")),
status: "generating".to_string(), status: "generating".to_string(),
revised_prompt: Some("lobster".to_string()), revised_prompt: Some("lobster".to_string()),
@@ -1391,6 +1432,8 @@ fn for_prompt_preserves_image_generation_calls_when_images_are_supported() {
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}, },
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -1405,6 +1448,7 @@ fn for_prompt_preserves_image_generation_calls_when_images_are_supported() {
history.for_prompt(&default_input_modalities()), history.for_prompt(&default_input_modalities()),
vec![ vec![
ResponseItem::ImageGenerationCall { ResponseItem::ImageGenerationCall {
encrypted_content: None,
id: Some(ResponseItemId::with_suffix("ig", "123")), id: Some(ResponseItemId::with_suffix("ig", "123")),
status: "generating".to_string(), status: "generating".to_string(),
revised_prompt: Some("lobster".to_string()), revised_prompt: Some("lobster".to_string()),
@@ -1412,6 +1456,8 @@ fn for_prompt_preserves_image_generation_calls_when_images_are_supported() {
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}, },
ResponseItem::Message { ResponseItem::Message {
encrypted_content: None,
status: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -1428,6 +1474,8 @@ fn for_prompt_preserves_image_generation_calls_when_images_are_supported() {
fn for_prompt_clears_image_generation_result_when_images_are_unsupported() { fn for_prompt_clears_image_generation_result_when_images_are_unsupported() {
let history = create_history_with_items(vec![ let history = create_history_with_items(vec![
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -1437,6 +1485,7 @@ fn for_prompt_clears_image_generation_result_when_images_are_unsupported() {
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}, },
ResponseItem::ImageGenerationCall { ResponseItem::ImageGenerationCall {
encrypted_content: None,
id: Some(ResponseItemId::with_suffix("ig", "123")), id: Some(ResponseItemId::with_suffix("ig", "123")),
status: "completed".to_string(), status: "completed".to_string(),
revised_prompt: Some("lobster".to_string()), revised_prompt: Some("lobster".to_string()),
@@ -1449,6 +1498,8 @@ fn for_prompt_clears_image_generation_result_when_images_are_unsupported() {
history.for_prompt(&[InputModality::Text]), history.for_prompt(&[InputModality::Text]),
vec![ vec![
ResponseItem::Message { ResponseItem::Message {
encrypted_content: None,
status: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -1458,6 +1509,7 @@ fn for_prompt_clears_image_generation_result_when_images_are_unsupported() {
internal_chat_message_metadata_passthrough: Some(unknown_content_metadata()), internal_chat_message_metadata_passthrough: Some(unknown_content_metadata()),
}, },
ResponseItem::ImageGenerationCall { ResponseItem::ImageGenerationCall {
encrypted_content: None,
id: Some(ResponseItemId::with_suffix("ig", "123")), id: Some(ResponseItemId::with_suffix("ig", "123")),
status: "completed".to_string(), status: "completed".to_string(),
revised_prompt: Some("lobster".to_string()), revised_prompt: Some("lobster".to_string()),
@@ -1514,6 +1566,8 @@ fn estimate_token_count_counts_recorded_base_instructions_once() {
fn remove_first_item_removes_matching_output_for_function_call() { fn remove_first_item_removes_matching_output_for_function_call() {
let items = vec![ let items = vec![
ResponseItem::FunctionCall { ResponseItem::FunctionCall {
status: None,
encrypted_content: None,
id: None, id: None,
name: "do_it".to_string(), name: "do_it".to_string(),
namespace: None, namespace: None,
@@ -1548,6 +1602,8 @@ fn remove_first_item_removes_matching_call_for_output() {
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}, },
ResponseItem::FunctionCall { ResponseItem::FunctionCall {
status: None,
encrypted_content: None,
id: None, id: None,
name: "do_it".to_string(), name: "do_it".to_string(),
namespace: None, namespace: None,
@@ -1566,6 +1622,7 @@ fn remove_first_item_removes_matching_call_for_output() {
fn remove_first_item_handles_local_shell_pair() { fn remove_first_item_handles_local_shell_pair() {
let items = vec![ let items = vec![
ResponseItem::LocalShellCall { ResponseItem::LocalShellCall {
encrypted_content: None,
id: None, id: None,
call_id: Some("call-3".to_string()), call_id: Some("call-3".to_string()),
status: LocalShellStatus::Completed, status: LocalShellStatus::Completed,
@@ -1652,6 +1709,8 @@ fn drop_last_n_user_turns_preserves_prefix() {
("steer", "Check the tests too."), ("steer", "Check the tests too."),
] { ] {
let message = ResponseItem::Message { let message = ResponseItem::Message {
status: None,
encrypted_content: None,
id: Some(ResponseItemId::with_suffix("msg", id)), id: Some(ResponseItemId::with_suffix("msg", id)),
role: "user".to_owned(), role: "user".to_owned(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -1880,6 +1939,8 @@ fn drop_last_n_user_turns_preserves_annotations_for_surviving_developer_fragment
let turn_id = "rolled-back-turn"; let turn_id = "rolled-back-turn";
let model_switch = ModelSwitchInstructions::new("switched model instructions").render(); let model_switch = ModelSwitchInstructions::new("switched model instructions").render();
let developer_message = ResponseItem::Message { let developer_message = ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "developer".to_string(), role: "developer".to_string(),
content: vec![ content: vec![
@@ -1916,6 +1977,8 @@ fn drop_last_n_user_turns_preserves_annotations_for_surviving_developer_fragment
assert_eq!( assert_eq!(
raw_items(&history), raw_items(&history),
vec![ResponseItem::Message { vec![ResponseItem::Message {
encrypted_content: None,
status: None,
id: None, id: None,
role: "developer".to_string(), role: "developer".to_string(),
content: vec![ content: vec![
@@ -2003,6 +2066,7 @@ fn drop_last_n_user_turns_clears_reference_context_for_mixed_developer_context_b
fn remove_first_item_handles_custom_tool_pair() { fn remove_first_item_handles_custom_tool_pair() {
let items = vec![ let items = vec![
ResponseItem::CustomToolCall { ResponseItem::CustomToolCall {
encrypted_content: None,
id: None, id: None,
status: None, status: None,
call_id: "tool-1".to_string(), call_id: "tool-1".to_string(),
@@ -2028,6 +2092,7 @@ fn remove_first_item_handles_custom_tool_pair() {
fn normalization_retains_local_shell_outputs() { fn normalization_retains_local_shell_outputs() {
let items = vec![ let items = vec![
ResponseItem::LocalShellCall { ResponseItem::LocalShellCall {
encrypted_content: None,
id: None, id: None,
call_id: Some("shell-1".to_string()), call_id: Some("shell-1".to_string()),
status: LocalShellStatus::Completed, status: LocalShellStatus::Completed,
@@ -2263,6 +2328,7 @@ fn format_exec_output_prefers_line_marker_when_both_limits_exceeded() {
#[test_case::test_case(true; "inherited")] #[test_case::test_case(true; "inherited")]
fn normalize_adds_missing_output_for_custom_tool_call(inherited: bool) { fn normalize_adds_missing_output_for_custom_tool_call(inherited: bool) {
let items = vec![ResponseItem::CustomToolCall { let items = vec![ResponseItem::CustomToolCall {
encrypted_content: None,
id: None, id: None,
status: None, status: None,
call_id: "tool-x".to_string(), call_id: "tool-x".to_string(),
@@ -2283,6 +2349,7 @@ fn normalize_adds_missing_output_for_custom_tool_call(inherited: bool) {
raw_items(&h), raw_items(&h),
vec![ vec![
ResponseItem::CustomToolCall { ResponseItem::CustomToolCall {
encrypted_content: None,
id: None, id: None,
status: None, status: None,
call_id: "tool-x".to_string(), call_id: "tool-x".to_string(),
@@ -2306,6 +2373,7 @@ fn normalize_adds_missing_output_for_custom_tool_call(inherited: bool) {
#[test_case::test_case(true; "inherited")] #[test_case::test_case(true; "inherited")]
fn normalize_adds_missing_output_for_local_shell_call_with_id(inherited: bool) { fn normalize_adds_missing_output_for_local_shell_call_with_id(inherited: bool) {
let items = vec![ResponseItem::LocalShellCall { let items = vec![ResponseItem::LocalShellCall {
encrypted_content: None,
id: None, id: None,
call_id: Some("shell-1".to_string()), call_id: Some("shell-1".to_string()),
status: LocalShellStatus::Completed, status: LocalShellStatus::Completed,
@@ -2330,6 +2398,7 @@ fn normalize_adds_missing_output_for_local_shell_call_with_id(inherited: bool) {
raw_items(&h), raw_items(&h),
vec![ vec![
ResponseItem::LocalShellCall { ResponseItem::LocalShellCall {
encrypted_content: None,
id: None, id: None,
call_id: Some("shell-1".to_string()), call_id: Some("shell-1".to_string()),
status: LocalShellStatus::Completed, status: LocalShellStatus::Completed,
@@ -2395,6 +2464,8 @@ fn normalize_mixed_inserts_and_removals() {
let items = vec![ let items = vec![
// Will get an inserted output // Will get an inserted output
ResponseItem::FunctionCall { ResponseItem::FunctionCall {
status: None,
encrypted_content: None,
id: None, id: None,
name: "f1".to_string(), name: "f1".to_string(),
namespace: None, namespace: None,
@@ -2414,6 +2485,7 @@ fn normalize_mixed_inserts_and_removals() {
}, },
// Will get an inserted custom tool output // Will get an inserted custom tool output
ResponseItem::CustomToolCall { ResponseItem::CustomToolCall {
encrypted_content: None,
id: None, id: None,
status: None, status: None,
call_id: "t1".to_string(), call_id: "t1".to_string(),
@@ -2424,6 +2496,7 @@ fn normalize_mixed_inserts_and_removals() {
}, },
// Local shell call also gets an inserted function call output // Local shell call also gets an inserted function call output
ResponseItem::LocalShellCall { ResponseItem::LocalShellCall {
encrypted_content: None,
id: None, id: None,
call_id: Some("s1".to_string()), call_id: Some("s1".to_string()),
status: LocalShellStatus::Completed, status: LocalShellStatus::Completed,
@@ -2445,6 +2518,8 @@ fn normalize_mixed_inserts_and_removals() {
raw_items(&h), raw_items(&h),
vec![ vec![
ResponseItem::FunctionCall { ResponseItem::FunctionCall {
encrypted_content: None,
status: None,
id: None, id: None,
name: "f1".to_string(), name: "f1".to_string(),
namespace: None, namespace: None,
@@ -2462,6 +2537,7 @@ fn normalize_mixed_inserts_and_removals() {
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}, },
ResponseItem::CustomToolCall { ResponseItem::CustomToolCall {
encrypted_content: None,
id: None, id: None,
status: None, status: None,
call_id: "t1".to_string(), call_id: "t1".to_string(),
@@ -2478,6 +2554,7 @@ fn normalize_mixed_inserts_and_removals() {
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}, },
ResponseItem::LocalShellCall { ResponseItem::LocalShellCall {
encrypted_content: None,
id: None, id: None,
call_id: Some("s1".to_string()), call_id: Some("s1".to_string()),
status: LocalShellStatus::Completed, status: LocalShellStatus::Completed,
@@ -2505,6 +2582,8 @@ fn normalize_mixed_inserts_and_removals() {
#[test] #[test]
fn normalize_adds_missing_output_for_function_call_inserts_output() { fn normalize_adds_missing_output_for_function_call_inserts_output() {
let items = vec![ResponseItem::FunctionCall { let items = vec![ResponseItem::FunctionCall {
status: None,
encrypted_content: None,
id: None, id: None,
name: "do_it".to_string(), name: "do_it".to_string(),
namespace: None, namespace: None,
@@ -2519,6 +2598,8 @@ fn normalize_adds_missing_output_for_function_call_inserts_output() {
raw_items(&h), raw_items(&h),
vec![ vec![
ResponseItem::FunctionCall { ResponseItem::FunctionCall {
encrypted_content: None,
status: None,
id: None, id: None,
name: "do_it".to_string(), name: "do_it".to_string(),
namespace: None, namespace: None,
@@ -2558,6 +2639,8 @@ fn normalize_preserves_named_function_call_output_without_call_id() {
fn for_prompt_assigns_stable_id_to_synthetic_output_without_reordering_history() { fn for_prompt_assigns_stable_id_to_synthetic_output_without_reordering_history() {
let items = vec![ let items = vec![
ResponseItem::FunctionCall { ResponseItem::FunctionCall {
status: None,
encrypted_content: None,
id: Some(ResponseItemId::with_suffix("fc", "existing")), id: Some(ResponseItemId::with_suffix("fc", "existing")),
name: "do_it".to_string(), name: "do_it".to_string(),
namespace: None, namespace: None,
@@ -2567,6 +2650,8 @@ fn for_prompt_assigns_stable_id_to_synthetic_output_without_reordering_history()
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}, },
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: Some(ResponseItemId::with_suffix("msg", "later")), id: Some(ResponseItemId::with_suffix("msg", "later")),
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -2601,6 +2686,7 @@ fn for_prompt_assigns_stable_id_to_synthetic_output_without_reordering_history()
#[test] #[test]
fn normalize_adds_missing_output_for_tool_search_call() { fn normalize_adds_missing_output_for_tool_search_call() {
let items = vec![ResponseItem::ToolSearchCall { let items = vec![ResponseItem::ToolSearchCall {
encrypted_content: None,
id: None, id: None,
call_id: Some("search-call-x".to_string()), call_id: Some("search-call-x".to_string()),
status: Some("completed".to_string()), status: Some("completed".to_string()),
@@ -2616,6 +2702,7 @@ fn normalize_adds_missing_output_for_tool_search_call() {
raw_items(&h), raw_items(&h),
vec![ vec![
ResponseItem::ToolSearchCall { ResponseItem::ToolSearchCall {
encrypted_content: None,
id: None, id: None,
call_id: Some("search-call-x".to_string()), call_id: Some("search-call-x".to_string()),
status: Some("completed".to_string()), status: Some("completed".to_string()),
@@ -2640,6 +2727,7 @@ fn normalize_adds_missing_output_for_tool_search_call() {
#[should_panic] #[should_panic]
fn normalize_adds_missing_output_for_custom_tool_call_panics_in_debug() { fn normalize_adds_missing_output_for_custom_tool_call_panics_in_debug() {
let items = vec![ResponseItem::CustomToolCall { let items = vec![ResponseItem::CustomToolCall {
encrypted_content: None,
id: None, id: None,
status: None, status: None,
call_id: "tool-x".to_string(), call_id: "tool-x".to_string(),
@@ -2657,6 +2745,7 @@ fn normalize_adds_missing_output_for_custom_tool_call_panics_in_debug() {
#[should_panic] #[should_panic]
fn normalize_adds_missing_output_for_local_shell_call_with_id_panics_in_debug() { fn normalize_adds_missing_output_for_local_shell_call_with_id_panics_in_debug() {
let items = vec![ResponseItem::LocalShellCall { let items = vec![ResponseItem::LocalShellCall {
encrypted_content: None,
id: None, id: None,
call_id: Some("shell-1".to_string()), call_id: Some("shell-1".to_string()),
status: LocalShellStatus::Completed, status: LocalShellStatus::Completed,
@@ -2771,6 +2860,8 @@ fn normalize_keeps_server_tool_search_output_without_matching_call() {
fn normalize_mixed_inserts_and_removals_panics_in_debug() { fn normalize_mixed_inserts_and_removals_panics_in_debug() {
let items = vec![ let items = vec![
ResponseItem::FunctionCall { ResponseItem::FunctionCall {
status: None,
encrypted_content: None,
id: None, id: None,
name: "f1".to_string(), name: "f1".to_string(),
namespace: None, namespace: None,
@@ -2788,6 +2879,7 @@ fn normalize_mixed_inserts_and_removals_panics_in_debug() {
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}, },
ResponseItem::CustomToolCall { ResponseItem::CustomToolCall {
encrypted_content: None,
id: None, id: None,
status: None, status: None,
call_id: "t1".to_string(), call_id: "t1".to_string(),
@@ -2797,6 +2889,7 @@ fn normalize_mixed_inserts_and_removals_panics_in_debug() {
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}, },
ResponseItem::LocalShellCall { ResponseItem::LocalShellCall {
encrypted_content: None,
id: None, id: None,
call_id: Some("s1".to_string()), call_id: Some("s1".to_string()),
status: LocalShellStatus::Completed, status: LocalShellStatus::Completed,
@@ -2819,6 +2912,8 @@ fn image_data_url_payload_does_not_dominate_message_estimate() {
let payload = "A".repeat(100_000); let payload = "A".repeat(100_000);
let image_url = format!("data:image/png;base64,{payload}"); let image_url = format!("data:image/png;base64,{payload}");
let image_item = ResponseItem::Message { let image_item = ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ content: vec![
@@ -2834,6 +2929,8 @@ fn image_data_url_payload_does_not_dominate_message_estimate() {
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}; };
let text_only_item = ResponseItem::Message { let text_only_item = ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -2857,6 +2954,8 @@ fn image_data_url_payload_does_not_dominate_message_estimate() {
#[test] #[test]
fn file_images_use_detail_appropriate_estimates() { fn file_images_use_detail_appropriate_estimates() {
let item = ResponseItem::Message { let item = ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ content: vec![
@@ -2956,6 +3055,8 @@ fn image_data_url_payload_does_not_dominate_custom_tool_call_output_estimate() {
fn audio_data_url_payload_does_not_dominate_message_estimate() { fn audio_data_url_payload_does_not_dominate_message_estimate() {
let (audio_url, _) = pcm_wav_data_url(/*sample_count*/ 801); let (audio_url, _) = pcm_wav_data_url(/*sample_count*/ 801);
let item = ResponseItem::Message { let item = ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputAudio { audio_url }], content: vec![ContentItem::InputAudio { audio_url }],
@@ -3021,6 +3122,8 @@ fn malformed_audio_data_url_falls_back_to_whole_url_size_cost() {
let audio_url = format!("data:audio/wav;base64,{payload}"); let audio_url = format!("data:audio/wav;base64,{payload}");
let fallback_bytes = approx_bytes_for_tokens(approx_token_count(&audio_url)) as i64; let fallback_bytes = approx_bytes_for_tokens(approx_token_count(&audio_url)) as i64;
let item = ResponseItem::Message { let item = ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputAudio { audio_url }], content: vec![ContentItem::InputAudio { audio_url }],
@@ -3076,6 +3179,8 @@ fn record_items_omits_audio_that_exceeds_the_output_budget() {
#[test] #[test]
fn non_base64_image_urls_use_image_estimates() { fn non_base64_image_urls_use_image_estimates() {
let message_item = ResponseItem::Message { let message_item = ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputImage { content: vec![ContentItem::InputImage {
@@ -3198,6 +3303,8 @@ fn encrypted_function_output_uses_plaintext_byte_estimate() {
#[test] #[test]
fn data_url_without_base64_marker_uses_image_estimate() { fn data_url_without_base64_marker_uses_image_estimate() {
let item = ResponseItem::Message { let item = ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputImage { content: vec![ContentItem::InputImage {
@@ -3246,6 +3353,8 @@ fn mixed_case_data_url_markers_are_adjusted() {
let payload = "F".repeat(1_024); let payload = "F".repeat(1_024);
let image_url = format!("DATA:image/png;BASE64,{payload}"); let image_url = format!("DATA:image/png;BASE64,{payload}");
let item = ResponseItem::Message { let item = ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputImage { content: vec![ContentItem::InputImage {
@@ -3269,6 +3378,8 @@ fn multiple_inline_images_apply_multiple_fixed_costs() {
let image_url_one = format!("data:image/png;base64,{payload_one}"); let image_url_one = format!("data:image/png;base64,{payload_one}");
let image_url_two = format!("data:image/jpeg;base64,{payload_two}"); let image_url_two = format!("data:image/jpeg;base64,{payload_two}");
let item = ResponseItem::Message { let item = ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ content: vec![
@@ -3405,9 +3516,13 @@ fn original_detail_webp_images_scale_with_dimensions() {
#[test] #[test]
fn text_only_items_count_decoded_content() { fn text_only_items_count_decoded_content() {
let item = ResponseItem::Message { let item = ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "assistant".to_string(), role: "assistant".to_string(),
content: vec![ContentItem::OutputText { content: vec![ContentItem::OutputText {
annotations: None,
logprobs: None,
text: "Hello, \"world\"!\nこんにちは".to_string(), text: "Hello, \"world\"!\nこんにちは".to_string(),
}], }],
phase: None, phase: None,
@@ -173,7 +173,7 @@ impl ContextManager {
let text = content let text = content
.iter() .iter()
.filter_map(|content| match content { .filter_map(|content| match content {
ContentItem::InputText { text } | ContentItem::OutputText { text } => { ContentItem::InputText { text } | ContentItem::OutputText { text, .. } => {
Some(text.as_str()) Some(text.as_str())
} }
_ => { _ => {
@@ -16,6 +16,8 @@ pub(crate) fn build_rendered_message(fragments: Vec<RenderedFragment>) -> Option
.unzip(); .unzip();
Some(ResponseItem::Message { Some(ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: role.to_string(), role: role.to_string(),
content, content,
@@ -45,6 +45,8 @@ fn fragment(role: &'static str, text: &'static str) -> TestFragment {
fn message(role: &str, texts: &[&str]) -> ResponseItem { fn message(role: &str, texts: &[&str]) -> ResponseItem {
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: role.to_string(), role: role.to_string(),
content: texts content: texts
+2 -2
View File
@@ -137,7 +137,7 @@ fn parse_user_message(message: &[ContentItem]) -> Option<UserMessageItem> {
audio_url: audio_url.clone(), audio_url: audio_url.clone(),
}); });
} }
ContentItem::OutputText { text } => { ContentItem::OutputText { text, .. } => {
warn!("Output text in user message: {}", text); warn!("Output text in user message: {}", text);
} }
} }
@@ -154,7 +154,7 @@ fn parse_agent_message(
let mut content: Vec<AgentMessageContent> = Vec::new(); let mut content: Vec<AgentMessageContent> = Vec::new();
for content_item in message.iter() { for content_item in message.iter() {
match content_item { match content_item {
ContentItem::InputText { text } | ContentItem::OutputText { text } => { ContentItem::InputText { text } | ContentItem::OutputText { text, .. } => {
content.push(AgentMessageContent::Text { text: text.clone() }); content.push(AgentMessageContent::Text { text: text.clone() });
} }
_ => { _ => {
+36
View File
@@ -77,6 +77,8 @@ fn parses_user_message_with_text_and_two_images() {
let img2 = "https://example.com/two.jpg".to_string(); let img2 = "https://example.com/two.jpg".to_string();
let item = ResponseItem::Message { let item = ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ content: vec![
@@ -128,6 +130,8 @@ fn parses_user_message_with_text_and_two_images() {
#[test] #[test]
fn parses_user_message_with_file_image() { fn parses_user_message_with_file_image() {
let item = ResponseItem::Message { let item = ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ content: vec![
@@ -174,6 +178,8 @@ fn skips_local_image_label_text() {
let user_text = "Please review this image.".to_string(); let user_text = "Please review this image.".to_string();
let item = ResponseItem::Message { let item = ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ content: vec![
@@ -222,6 +228,8 @@ fn skips_local_audio_label_text() {
let user_text = "Please transcribe this audio.".to_string(); let user_text = "Please transcribe this audio.".to_string();
let item = ResponseItem::Message { let item = ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ content: vec![
@@ -262,6 +270,8 @@ fn skips_local_audio_label_text() {
#[test] #[test]
fn parses_assistant_message_input_text_for_backward_compatibility() { fn parses_assistant_message_input_text_for_backward_compatibility() {
let item = ResponseItem::Message { let item = ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "assistant".to_string(), role: "assistant".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -303,6 +313,8 @@ fn skips_unnamed_image_label_text() {
let user_text = "Please review this image.".to_string(); let user_text = "Please review this image.".to_string();
let item = ResponseItem::Message { let item = ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ content: vec![
@@ -348,6 +360,8 @@ fn skips_unnamed_image_label_text() {
fn skips_user_instructions_and_env() { fn skips_user_instructions_and_env() {
let items = vec![ let items = vec![
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -356,6 +370,8 @@ fn skips_user_instructions_and_env() {
phase: None, phase: None,
internal_chat_message_metadata_passthrough: None,}, internal_chat_message_metadata_passthrough: None,},
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -364,6 +380,8 @@ fn skips_user_instructions_and_env() {
phase: None, phase: None,
internal_chat_message_metadata_passthrough: None,}, internal_chat_message_metadata_passthrough: None,},
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -372,6 +390,8 @@ fn skips_user_instructions_and_env() {
phase: None, phase: None,
internal_chat_message_metadata_passthrough: None,}, internal_chat_message_metadata_passthrough: None,},
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -381,6 +401,8 @@ fn skips_user_instructions_and_env() {
phase: None, phase: None,
internal_chat_message_metadata_passthrough: None,}, internal_chat_message_metadata_passthrough: None,},
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -389,6 +411,8 @@ fn skips_user_instructions_and_env() {
phase: None, phase: None,
internal_chat_message_metadata_passthrough: None,}, internal_chat_message_metadata_passthrough: None,},
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ content: vec![
@@ -439,6 +463,8 @@ fn parses_hook_prompt_message_as_distinct_turn_item() {
#[test] #[test]
fn parses_hook_prompt_and_hides_other_contextual_fragments() { fn parses_hook_prompt_and_hides_other_contextual_fragments() {
let item = ResponseItem::Message { let item = ResponseItem::Message {
status: None,
encrypted_content: None,
id: Some(ResponseItemId::with_suffix("msg", "1")), id: Some(ResponseItemId::with_suffix("msg", "1")),
role: "user".to_string(), role: "user".to_string(),
content: vec![ content: vec![
@@ -474,6 +500,8 @@ fn parses_hook_prompt_and_hides_other_contextual_fragments() {
#[test] #[test]
fn internal_model_context_does_not_parse_as_visible_turn_item() { fn internal_model_context_does_not_parse_as_visible_turn_item() {
let item = ResponseItem::Message { let item = ResponseItem::Message {
status: None,
encrypted_content: None,
id: Some(ResponseItemId::with_suffix("msg", "1")), id: Some(ResponseItemId::with_suffix("msg", "1")),
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -493,9 +521,13 @@ fn internal_model_context_does_not_parse_as_visible_turn_item() {
#[test] #[test]
fn parses_agent_message() { fn parses_agent_message() {
let item = ResponseItem::Message { let item = ResponseItem::Message {
status: None,
encrypted_content: None,
id: Some(ResponseItemId::with_suffix("msg", "1")), id: Some(ResponseItemId::with_suffix("msg", "1")),
role: "assistant".to_string(), role: "assistant".to_string(),
content: vec![ContentItem::OutputText { content: vec![ContentItem::OutputText {
annotations: None,
logprobs: None,
text: "Hello from Codex".to_string(), text: "Hello from Codex".to_string(),
}], }],
phase: None, phase: None,
@@ -584,6 +616,7 @@ fn parses_reasoning_including_raw_content() {
#[test] #[test]
fn parses_web_search_call() { fn parses_web_search_call() {
let item = ResponseItem::WebSearchCall { let item = ResponseItem::WebSearchCall {
encrypted_content: None,
id: Some(ResponseItemId::with_suffix("ws", "1")), id: Some(ResponseItemId::with_suffix("ws", "1")),
status: Some("completed".to_string()), status: Some("completed".to_string()),
action: Some(WebSearchAction::Search { action: Some(WebSearchAction::Search {
@@ -615,6 +648,7 @@ fn parses_web_search_call() {
#[test] #[test]
fn parses_web_search_open_page_call() { fn parses_web_search_open_page_call() {
let item = ResponseItem::WebSearchCall { let item = ResponseItem::WebSearchCall {
encrypted_content: None,
id: Some(ResponseItemId::with_suffix("ws", "open")), id: Some(ResponseItemId::with_suffix("ws", "open")),
status: Some("completed".to_string()), status: Some("completed".to_string()),
action: Some(WebSearchAction::OpenPage { action: Some(WebSearchAction::OpenPage {
@@ -644,6 +678,7 @@ fn parses_web_search_open_page_call() {
#[test] #[test]
fn parses_web_search_find_in_page_call() { fn parses_web_search_find_in_page_call() {
let item = ResponseItem::WebSearchCall { let item = ResponseItem::WebSearchCall {
encrypted_content: None,
id: Some(ResponseItemId::with_suffix("ws", "find")), id: Some(ResponseItemId::with_suffix("ws", "find")),
status: Some("completed".to_string()), status: Some("completed".to_string()),
action: Some(WebSearchAction::FindInPage { action: Some(WebSearchAction::FindInPage {
@@ -675,6 +710,7 @@ fn parses_web_search_find_in_page_call() {
#[test] #[test]
fn parses_partial_web_search_call_without_action_as_other() { fn parses_partial_web_search_call_without_action_as_other() {
let item = ResponseItem::WebSearchCall { let item = ResponseItem::WebSearchCall {
encrypted_content: None,
id: Some(ResponseItemId::with_suffix("ws", "partial")), id: Some(ResponseItemId::with_suffix("ws", "partial")),
status: Some("in_progress".to_string()), status: Some("in_progress".to_string()),
action: None, action: None,
+79 -1
View File
@@ -254,6 +254,8 @@ async fn seed_guardian_parent_history(session: &Arc<Session>, turn: &Arc<TurnCon
turn.model_info(), turn.model_info(),
&[ &[
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -264,6 +266,8 @@ async fn seed_guardian_parent_history(session: &Arc<Session>, turn: &Arc<TurnCon
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}, },
ResponseItem::FunctionCall { ResponseItem::FunctionCall {
status: None,
encrypted_content: None,
id: None, id: None,
name: "gh_repo_view".to_string(), name: "gh_repo_view".to_string(),
namespace: None, namespace: None,
@@ -283,9 +287,13 @@ async fn seed_guardian_parent_history(session: &Arc<Session>, turn: &Arc<TurnCon
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}, },
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "assistant".to_string(), role: "assistant".to_string(),
content: vec![ContentItem::OutputText { content: vec![ContentItem::OutputText {
annotations: None,
logprobs: None,
text: "The repo is public; I now need approval to push the docs fix." text: "The repo is public; I now need approval to push the docs fix."
.to_string(), .to_string(),
}], }],
@@ -302,7 +310,9 @@ fn response_item_contains_message_text(item: &ResponseItem, needle: &str) -> boo
return false; return false;
}; };
content.iter().any(|item| match item { content.iter().any(|item| match item {
ContentItem::InputText { text } | ContentItem::OutputText { text } => text.contains(needle), ContentItem::InputText { text } | ContentItem::OutputText { text, .. } => {
text.contains(needle)
}
ContentItem::InputImage { .. } | ContentItem::InputAudio { .. } => false, ContentItem::InputImage { .. } | ContentItem::InputAudio { .. } => false,
}) })
} }
@@ -775,6 +785,8 @@ async fn build_guardian_prompt_delta_mode_preserves_original_numbering() -> anyh
turn.model_info(), turn.model_info(),
&[ &[
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -784,9 +796,13 @@ async fn build_guardian_prompt_delta_mode_preserves_original_numbering() -> anyh
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}, },
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "assistant".to_string(), role: "assistant".to_string(),
content: vec![ContentItem::OutputText { content: vec![ContentItem::OutputText {
annotations: None,
logprobs: None,
text: "I need approval for the second push.".to_string(), text: "I need approval for the second push.".to_string(),
}], }],
phase: None, phase: None,
@@ -916,6 +932,8 @@ async fn build_guardian_prompt_stale_delta_version_falls_back_to_full_prompt() -
.replace_history( .replace_history(
vec![ vec![
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -925,9 +943,13 @@ async fn build_guardian_prompt_stale_delta_version_falls_back_to_full_prompt() -
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}, },
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "assistant".to_string(), role: "assistant".to_string(),
content: vec![ContentItem::OutputText { content: vec![ContentItem::OutputText {
annotations: None,
logprobs: None,
text: "Compacted summary of earlier guardian context.".to_string(), text: "Compacted summary of earlier guardian context.".to_string(),
}], }],
phase: None, phase: None,
@@ -943,6 +965,8 @@ async fn build_guardian_prompt_stale_delta_version_falls_back_to_full_prompt() -
turn.model_info(), turn.model_info(),
&[ &[
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -952,9 +976,13 @@ async fn build_guardian_prompt_stale_delta_version_falls_back_to_full_prompt() -
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}, },
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "assistant".to_string(), role: "assistant".to_string(),
content: vec![ContentItem::OutputText { content: vec![ContentItem::OutputText {
annotations: None,
logprobs: None,
text: "I need approval for the post-compaction push.".to_string(), text: "I need approval for the post-compaction push.".to_string(),
}], }],
phase: None, phase: None,
@@ -1021,6 +1049,8 @@ fn collect_guardian_transcript_entries(
fn collect_guardian_transcript_entries_skips_contextual_user_messages() { fn collect_guardian_transcript_entries_skips_contextual_user_messages() {
let items = vec![ let items = vec![
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -1030,9 +1060,13 @@ fn collect_guardian_transcript_entries_skips_contextual_user_messages() {
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}, },
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "assistant".to_string(), role: "assistant".to_string(),
content: vec![ContentItem::OutputText { content: vec![ContentItem::OutputText {
annotations: None,
logprobs: None,
text: "hello".to_string(), text: "hello".to_string(),
}], }],
phase: None, phase: None,
@@ -1058,6 +1092,8 @@ fn collect_guardian_transcript_entries_skips_contextual_user_messages() {
fn collect_guardian_transcript_entries_includes_recent_tool_calls_and_output() { fn collect_guardian_transcript_entries_includes_recent_tool_calls_and_output() {
let mut items = vec![ let mut items = vec![
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -1067,6 +1103,8 @@ fn collect_guardian_transcript_entries_includes_recent_tool_calls_and_output() {
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}, },
ResponseItem::FunctionCall { ResponseItem::FunctionCall {
status: None,
encrypted_content: None,
id: None, id: None,
name: "read_file".to_string(), name: "read_file".to_string(),
namespace: None, namespace: None,
@@ -1086,9 +1124,13 @@ fn collect_guardian_transcript_entries_includes_recent_tool_calls_and_output() {
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}, },
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "assistant".to_string(), role: "assistant".to_string(),
content: vec![ContentItem::OutputText { content: vec![ContentItem::OutputText {
annotations: None,
logprobs: None,
text: "I need to push a fix".to_string(), text: "I need to push a fix".to_string(),
}], }],
phase: None, phase: None,
@@ -1762,6 +1804,8 @@ fn build_guardian_transcript_reserves_separate_budget_for_tool_evidence() {
] ]
.into_iter() .into_iter()
.map(|(role, text)| ResponseItem::Message { .map(|(role, text)| ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: role.to_string(), role: role.to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -1772,6 +1816,8 @@ fn build_guardian_transcript_reserves_separate_budget_for_tool_evidence() {
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
items.extend((0..12).map(|index| ResponseItem::FunctionCall { items.extend((0..12).map(|index| ResponseItem::FunctionCall {
status: None,
encrypted_content: None,
id: None, id: None,
name: format!("tool_{index}"), name: format!("tool_{index}"),
namespace: None, namespace: None,
@@ -1815,6 +1861,8 @@ fn build_guardian_transcript_preserves_recent_tool_context_when_user_history_is_
let repeated = "authorization ".repeat(6_000); let repeated = "authorization ".repeat(6_000);
let mut items = (0..8) let mut items = (0..8)
.map(|_| ResponseItem::Message { .map(|_| ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -1826,6 +1874,8 @@ fn build_guardian_transcript_preserves_recent_tool_context_when_user_history_is_
.collect::<Vec<_>>(); .collect::<Vec<_>>();
items.extend([ items.extend([
ResponseItem::FunctionCall { ResponseItem::FunctionCall {
status: None,
encrypted_content: None,
id: None, id: None,
name: "shell".to_string(), name: "shell".to_string(),
namespace: None, namespace: None,
@@ -2269,6 +2319,8 @@ async fn guardian_review_request_layout_matches_model_visible_request_snapshot()
turn.as_ref(), turn.as_ref(),
turn.model_info(), turn.model_info(),
&[ResponseItem::Message { &[ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -2555,6 +2607,8 @@ async fn guardian_reuses_prompt_cache_key_and_appends_prior_reviews() -> anyhow:
turn.model_info(), turn.model_info(),
&[ &[
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -2564,9 +2618,13 @@ async fn guardian_reuses_prompt_cache_key_and_appends_prior_reviews() -> anyhow:
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}, },
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "assistant".to_string(), role: "assistant".to_string(),
content: vec![ContentItem::OutputText { content: vec![ContentItem::OutputText {
annotations: None,
logprobs: None,
text: "I need approval for the second docs fix.".to_string(), text: "I need approval for the second docs fix.".to_string(),
}], }],
phase: None, phase: None,
@@ -2642,6 +2700,8 @@ async fn guardian_reuses_prompt_cache_key_and_appends_prior_reviews() -> anyhow:
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}, },
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -2651,9 +2711,13 @@ async fn guardian_reuses_prompt_cache_key_and_appends_prior_reviews() -> anyhow:
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}, },
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "assistant".to_string(), role: "assistant".to_string(),
content: vec![ContentItem::OutputText { content: vec![ContentItem::OutputText {
annotations: None,
logprobs: None,
text: "I need approval for the third docs fix.".to_string(), text: "I need approval for the third docs fix.".to_string(),
}], }],
phase: None, phase: None,
@@ -2702,6 +2766,8 @@ async fn guardian_reuses_prompt_cache_key_and_appends_prior_reviews() -> anyhow:
session session
.replace_history( .replace_history(
vec![ResponseItem::Message { vec![ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -3632,6 +3698,8 @@ async fn guardian_ephemeral_retry_preserves_parallel_trunk_and_fork_history() ->
turn.as_ref(), turn.model_info(), turn.as_ref(), turn.model_info(),
&[ &[
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -3640,9 +3708,13 @@ async fn guardian_ephemeral_retry_preserves_parallel_trunk_and_fork_history() ->
phase: None, phase: None,
internal_chat_message_metadata_passthrough: None,}, internal_chat_message_metadata_passthrough: None,},
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "assistant".to_string(), role: "assistant".to_string(),
content: vec![ContentItem::OutputText { content: vec![ContentItem::OutputText {
annotations: None,
logprobs: None,
text: "I need approval to run git diff.".to_string(), text: "I need approval to run git diff.".to_string(),
}], }],
phase: None, phase: None,
@@ -3709,6 +3781,8 @@ async fn guardian_ephemeral_retry_preserves_parallel_trunk_and_fork_history() ->
turn.as_ref(), turn.model_info(), turn.as_ref(), turn.model_info(),
&[ &[
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -3717,9 +3791,13 @@ async fn guardian_ephemeral_retry_preserves_parallel_trunk_and_fork_history() ->
phase: None, phase: None,
internal_chat_message_metadata_passthrough: None,}, internal_chat_message_metadata_passthrough: None,},
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "assistant".to_string(), role: "assistant".to_string(),
content: vec![ContentItem::OutputText { content: vec![ContentItem::OutputText {
annotations: None,
logprobs: None,
text: "I need approval to push after the diff check.".to_string(), text: "I need approval to push after the diff check.".to_string(),
}], }],
phase: None, phase: None,
@@ -67,6 +67,8 @@ fn decoded_image(image_url: &str) -> (Vec<u8>, DynamicImage) {
async fn preparation_preserves_small_image_bytes_and_replaces_remote_urls() { async fn preparation_preserves_small_image_bytes_and_replaces_remote_urls() {
let (data_url, original_bytes) = png_data_url(/*width*/ 64, /*height*/ 32); let (data_url, original_bytes) = png_data_url(/*width*/ 64, /*height*/ 32);
let mut items = vec![ResponseItem::Message { let mut items = vec![ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ content: vec![
@@ -87,6 +89,8 @@ async fn preparation_preserves_small_image_bytes_and_replaces_remote_urls() {
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}]; }];
items.push(ResponseItem::Message { items.push(ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "developer".to_string(), role: "developer".to_string(),
content: vec![ContentItem::InputImage { content: vec![ContentItem::InputImage {
@@ -127,6 +131,8 @@ async fn preparation_preserves_small_image_bytes_and_replaces_remote_urls() {
assert_eq!( assert_eq!(
&items[1], &items[1],
&ResponseItem::Message { &ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "developer".to_string(), role: "developer".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -176,6 +182,8 @@ async fn detail_policies_apply_the_expected_budgets() {
] { ] {
let (image_url, _) = png_data_url(input_dimensions.0, input_dimensions.1); let (image_url, _) = png_data_url(input_dimensions.0, input_dimensions.1);
let mut items = vec![ResponseItem::Message { let mut items = vec![ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputImage { content: vec![ContentItem::InputImage {
@@ -269,6 +277,8 @@ async fn preparation_reports_tool_output_item_id() {
async fn upload_failure_keeps_resized_image_inline() { async fn upload_failure_keeps_resized_image_inline() {
let (image_url, _) = png_data_url(/*width*/ 2048, /*height*/ 2048); let (image_url, _) = png_data_url(/*width*/ 2048, /*height*/ 2048);
let mut items = vec![ResponseItem::Message { let mut items = vec![ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputImage { content: vec![ContentItem::InputImage {
@@ -311,6 +321,8 @@ async fn resize_notices_count_file_backed_images_and_skip_failed_images() {
let (small_image_url, _) = png_data_url(/*width*/ 64, /*height*/ 32); let (small_image_url, _) = png_data_url(/*width*/ 64, /*height*/ 32);
let mut items = vec![ let mut items = vec![
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ content: vec![
@@ -456,6 +468,8 @@ async fn resize_notices_count_file_backed_images_and_skip_failed_images() {
assert_eq!( assert_eq!(
&items[1], &items[1],
&ResponseItem::Message { &ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "developer".to_string(), role: "developer".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -504,6 +518,8 @@ async fn resize_notices_count_file_backed_images_and_skip_failed_images() {
assert_eq!( assert_eq!(
&items[3], &items[3],
&ResponseItem::Message { &ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "developer".to_string(), role: "developer".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
+10 -1
View File
@@ -81,6 +81,8 @@ fn stored_thread(cwd: &str, title: &str, first_user_message: &str) -> StoredThre
fn message(role: &str, content: ContentItem) -> ResponseItem { fn message(role: &str, content: ContentItem) -> ResponseItem {
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: role.to_string(), role: role.to_string(),
content: vec![content], content: vec![content],
@@ -94,7 +96,14 @@ fn user_message(text: impl Into<String>) -> ResponseItem {
} }
fn assistant_message(text: impl Into<String>) -> ResponseItem { fn assistant_message(text: impl Into<String>) -> ResponseItem {
message("assistant", ContentItem::OutputText { text: text.into() }) message(
"assistant",
ContentItem::OutputText {
annotations: None,
logprobs: None,
text: text.into(),
},
)
} }
fn long_turn_text(index: usize) -> String { fn long_turn_text(index: usize) -> String {
@@ -204,6 +204,8 @@ macro_rules! object {
fn user_message(text: &str) -> ResponseItem { fn user_message(text: &str) -> ResponseItem {
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -216,9 +218,13 @@ fn user_message(text: &str) -> ResponseItem {
fn assistant_message(text: &str) -> ResponseItem { fn assistant_message(text: &str) -> ResponseItem {
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "assistant".to_string(), role: "assistant".to_string(),
content: vec![ContentItem::OutputText { content: vec![ContentItem::OutputText {
annotations: None,
logprobs: None,
text: text.to_string(), text: text.to_string(),
}], }],
phase: None, phase: None,
@@ -239,9 +245,13 @@ fn inter_agent_assistant_message(text: &str) -> ResponseItem {
/*trigger_turn*/ true, /*trigger_turn*/ true,
); );
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "assistant".to_string(), role: "assistant".to_string(),
content: vec![ContentItem::OutputText { content: vec![ContentItem::OutputText {
annotations: None,
logprobs: None,
text: serde_json::to_string(&communication).unwrap(), text: serde_json::to_string(&communication).unwrap(),
}], }],
phase: None, phase: None,
+1
View File
@@ -1776,6 +1776,7 @@ impl Session {
config config
.features .features
.enabled(Feature::ConcurrentReasoningSummaries), .enabled(Feature::ConcurrentReasoningSummaries),
config.features.enabled(Feature::OutputTokenReplay),
attestation_provider, attestation_provider,
config.http_client_factory(), config.http_client_factory(),
workspace_routing.as_ref().clone(), workspace_routing.as_ref().clone(),
+30
View File
@@ -322,6 +322,8 @@ mod guardian_tests;
fn user_message(text: &str) -> ResponseItem { fn user_message(text: &str) -> ResponseItem {
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -391,9 +393,13 @@ async fn default_turn_context_assigns_missing_response_item_ids() {
fn assistant_message(text: &str) -> ResponseItem { fn assistant_message(text: &str) -> ResponseItem {
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "assistant".to_string(), role: "assistant".to_string(),
content: vec![ContentItem::OutputText { content: vec![ContentItem::OutputText {
annotations: None,
logprobs: None,
text: text.to_string(), text: text.to_string(),
}], }],
phase: None, phase: None,
@@ -589,6 +595,8 @@ async fn world_state_extension_metrics_follow_turn_model_switch() {
fn skill_message(text: &str) -> ResponseItem { fn skill_message(text: &str) -> ResponseItem {
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -853,6 +861,7 @@ fn test_model_client_session() -> crate::client::ModelClientSession {
/*include_timing_metrics*/ false, /*include_timing_metrics*/ false,
/*beta_features_header*/ None, /*beta_features_header*/ None,
/*concurrent_reasoning_summaries_enabled*/ false, /*concurrent_reasoning_summaries_enabled*/ false,
/*output_token_replay_enabled*/ false,
/*attestation_provider*/ None, /*attestation_provider*/ None,
HttpClientFactory::new(OutboundProxyPolicy::ReqwestDefault), HttpClientFactory::new(OutboundProxyPolicy::ReqwestDefault),
codex_model_provider::WorkspaceRoutingContext::new( codex_model_provider::WorkspaceRoutingContext::new(
@@ -2426,6 +2435,8 @@ async fn reconstruct_history_matches_live_compactions() {
async fn reconstruct_history_uses_replacement_history_verbatim() { async fn reconstruct_history_uses_replacement_history_verbatim() {
let (session, turn_context) = make_session_and_context().await; let (session, turn_context) = make_session_and_context().await;
let summary_item = ResponseItem::Message { let summary_item = ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -2443,6 +2454,8 @@ async fn reconstruct_history_uses_replacement_history_verbatim() {
metadata: Some(CodexHarnessMetadata::default()), metadata: Some(CodexHarnessMetadata::default()),
}, },
ResponseItemEnvelope::new(ResponseItem::Message { ResponseItemEnvelope::new(ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "developer".to_string(), role: "developer".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -3095,6 +3108,8 @@ async fn prepares_image_failures_before_history_insertion() {
async fn prepares_resumed_history_before_installing_it() { async fn prepares_resumed_history_before_installing_it() {
let (session, _turn_context) = make_session_and_context().await; let (session, _turn_context) = make_session_and_context().await;
let resumed_item = ResponseItem::Message { let resumed_item = ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ content: vec![
@@ -3134,6 +3149,8 @@ async fn prepares_resumed_history_before_installing_it() {
assert_eq!( assert_eq!(
raw_history_items(&history), raw_history_items(&history),
vec![ResponseItem::Message { vec![ResponseItem::Message {
encrypted_content: None,
status: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ content: vec![
@@ -4114,6 +4131,8 @@ async fn record_initial_history_assigns_and_persists_id_for_forked_response_item
let response_item = let response_item =
ContextualUserFragment::into(DeveloperInstructions::new("Subagent guidance.")); ContextualUserFragment::into(DeveloperInstructions::new("Subagent guidance."));
let mut expected_item = ResponseItem::Message { let mut expected_item = ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "developer".to_string(), role: "developer".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -6907,6 +6926,7 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
config config
.features .features
.enabled(Feature::ConcurrentReasoningSummaries), .enabled(Feature::ConcurrentReasoningSummaries),
/*output_token_replay_enabled*/ false,
/*attestation_provider*/ None, /*attestation_provider*/ None,
config.http_client_factory(), config.http_client_factory(),
config.workspace_routing_context(), config.workspace_routing_context(),
@@ -9174,6 +9194,7 @@ where
config config
.features .features
.enabled(Feature::ConcurrentReasoningSummaries), .enabled(Feature::ConcurrentReasoningSummaries),
/*output_token_replay_enabled*/ false,
/*attestation_provider*/ None, /*attestation_provider*/ None,
config.http_client_factory(), config.http_client_factory(),
config.workspace_routing_context(), config.workspace_routing_context(),
@@ -11135,6 +11156,8 @@ async fn record_context_updates_and_set_reference_context_item_reinjects_full_co
let turn_context = Arc::new(turn_context); let turn_context = Arc::new(turn_context);
let step_context = StepContext::for_test(Arc::clone(&turn_context)); let step_context = StepContext::for_test(Arc::clone(&turn_context));
let compacted_summary = ResponseItem::Message { let compacted_summary = ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -12479,6 +12502,8 @@ async fn task_finish_emits_turn_item_lifecycle_for_leftover_pending_user_input()
let history = sess.clone_history().await; let history = sess.clone_history().await;
let expected = ResponseItem::Message { let expected = ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ content: vec![
@@ -12663,6 +12688,8 @@ async fn thread_idle_lifecycle_waits_for_trigger_turn_mailbox_work() {
async fn abort_empty_active_turn_preserves_pending_input() { async fn abort_empty_active_turn_preserves_pending_input() {
let (sess, _tc, _rx) = make_session_and_context_with_rx().await; let (sess, _tc, _rx) = make_session_and_context_with_rx().await;
let pending_item = ResponseItem::Message { let pending_item = ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -13003,6 +13030,8 @@ async fn tool_calls_reopen_mailbox_delivery_for_current_turn() {
.await; .await;
let item = ResponseItem::FunctionCall { let item = ResponseItem::FunctionCall {
status: None,
encrypted_content: None,
id: None, id: None,
name: "test_tool".to_string(), name: "test_tool".to_string(),
namespace: None, namespace: None,
@@ -13122,6 +13151,7 @@ async fn fatal_tool_error_stops_turn_and_reports_error() {
&Default::default(), &Default::default(),
); );
let item = ResponseItem::CustomToolCall { let item = ResponseItem::CustomToolCall {
encrypted_content: None,
id: None, id: None,
status: None, status: None,
call_id: "call-1".to_string(), call_id: "call-1".to_string(),
+1 -1
View File
@@ -2321,7 +2321,7 @@ async fn maybe_complete_plan_item_from_message(
{ {
let mut text = String::new(); let mut text = String::new();
for entry in content { for entry in content {
if let ContentItem::OutputText { text: chunk } = entry { if let ContentItem::OutputText { text: chunk, .. } = entry {
text.push_str(chunk); text.push_str(chunk);
} }
} }
@@ -64,6 +64,8 @@ impl SessionTask for NeverEndingTask {
fn user_message(text: &str) -> ResponseItem { fn user_message(text: &str) -> ResponseItem {
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
+4
View File
@@ -29,9 +29,13 @@ impl TurnItemContributor for RewriteAgentMessageContributor {
fn assistant_output_text(text: &str) -> ResponseItem { fn assistant_output_text(text: &str) -> ResponseItem {
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: Some(ResponseItemId::with_suffix("msg", "1")), id: Some(ResponseItemId::with_suffix("msg", "1")),
role: "assistant".to_string(), role: "assistant".to_string(),
content: vec![ContentItem::OutputText { content: vec![ContentItem::OutputText {
annotations: None,
logprobs: None,
text: text.to_string(), text: text.to_string(),
}], }],
phase: None, phase: None,
+1 -1
View File
@@ -66,7 +66,7 @@ pub(crate) fn raw_assistant_output_text_from_item(item: &ResponseItem) -> Option
let combined = content let combined = content
.iter() .iter()
.filter_map(|ci| match ci { .filter_map(|ci| match ci {
codex_protocol::models::ContentItem::OutputText { text } => Some(text.as_str()), codex_protocol::models::ContentItem::OutputText { text, .. } => Some(text.as_str()),
_ => None, _ => None,
}) })
.collect::<String>(); .collect::<String>();
@@ -41,9 +41,13 @@ fn assistant_output_text(text: &str) -> ResponseItem {
fn assistant_output_text_with_phase(text: &str, phase: Option<MessagePhase>) -> ResponseItem { fn assistant_output_text_with_phase(text: &str, phase: Option<MessagePhase>) -> ResponseItem {
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: Some(ResponseItemId::with_suffix("msg", "1")), id: Some(ResponseItemId::with_suffix("msg", "1")),
role: "assistant".to_string(), role: "assistant".to_string(),
content: vec![ContentItem::OutputText { content: vec![ContentItem::OutputText {
annotations: None,
logprobs: None,
text: text.to_string(), text: text.to_string(),
}], }],
phase, phase,
@@ -55,12 +59,14 @@ fn assistant_output_text_with_phase(text: &str, phase: Option<MessagePhase>) ->
fn external_context_pollution_items_include_web_search_and_tool_search() { fn external_context_pollution_items_include_web_search_and_tool_search() {
let polluting_items = [ let polluting_items = [
ResponseItem::WebSearchCall { ResponseItem::WebSearchCall {
encrypted_content: None,
id: None, id: None,
status: Some("completed".to_string()), status: Some("completed".to_string()),
action: None, action: None,
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}, },
ResponseItem::ToolSearchCall { ResponseItem::ToolSearchCall {
encrypted_content: None,
id: None, id: None,
call_id: Some("search-1".to_string()), call_id: Some("search-1".to_string()),
status: None, status: None,
@@ -97,6 +103,7 @@ fn external_context_pollution_items_include_web_search_and_tool_search() {
fn external_context_pollution_items_exclude_local_tool_calls() { fn external_context_pollution_items_exclude_local_tool_calls() {
let non_polluting_items = [ let non_polluting_items = [
ResponseItem::LocalShellCall { ResponseItem::LocalShellCall {
encrypted_content: None,
id: None, id: None,
call_id: Some("shell-1".to_string()), call_id: Some("shell-1".to_string()),
status: LocalShellStatus::Completed, status: LocalShellStatus::Completed,
@@ -110,6 +117,8 @@ fn external_context_pollution_items_exclude_local_tool_calls() {
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}, },
ResponseItem::FunctionCall { ResponseItem::FunctionCall {
status: None,
encrypted_content: None,
id: None, id: None,
name: "shell".to_string(), name: "shell".to_string(),
namespace: None, namespace: None,
@@ -127,6 +136,7 @@ fn external_context_pollution_items_exclude_local_tool_calls() {
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}, },
ResponseItem::CustomToolCall { ResponseItem::CustomToolCall {
encrypted_content: None,
id: None, id: None,
status: None, status: None,
call_id: "custom-1".to_string(), call_id: "custom-1".to_string(),
+6
View File
@@ -240,6 +240,8 @@ pub(crate) async fn exit_review_mode(
&ctx, &ctx,
ctx.model_info(), ctx.model_info(),
&[ResponseItem::Message { &[ResponseItem::Message {
status: None,
encrypted_content: None,
id: Some(ResponseItemId::new("msg")), id: Some(ResponseItemId::new("msg")),
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { text: user_message }], content: vec![ContentItem::InputText { text: user_message }],
@@ -260,9 +262,13 @@ pub(crate) async fn exit_review_mode(
ctx.as_ref(), ctx.as_ref(),
ctx.model_info(), ctx.model_info(),
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: Some(ResponseItemId::new("msg")), id: Some(ResponseItemId::new("msg")),
role: "assistant".to_string(), role: "assistant".to_string(),
content: vec![ContentItem::OutputText { content: vec![ContentItem::OutputText {
annotations: None,
logprobs: None,
text: assistant_message, text: assistant_message,
}], }],
phase: None, phase: None,
+12
View File
@@ -582,9 +582,13 @@ impl codex_agent_graph_store::AgentGraphStore for FakeAgentGraphStore {
fn user_msg(text: &str) -> ResponseItem { fn user_msg(text: &str) -> ResponseItem {
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::OutputText { content: vec![ContentItem::OutputText {
annotations: None,
logprobs: None,
text: text.to_string(), text: text.to_string(),
}], }],
phase: None, phase: None,
@@ -593,9 +597,13 @@ fn user_msg(text: &str) -> ResponseItem {
} }
fn assistant_msg(text: &str) -> ResponseItem { fn assistant_msg(text: &str) -> ResponseItem {
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "assistant".to_string(), role: "assistant".to_string(),
content: vec![ContentItem::OutputText { content: vec![ContentItem::OutputText {
annotations: None,
logprobs: None,
text: text.to_string(), text: text.to_string(),
}], }],
phase: None, phase: None,
@@ -696,6 +704,8 @@ fn truncates_before_requested_user_message() {
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}, },
ResponseItem::FunctionCall { ResponseItem::FunctionCall {
status: None,
encrypted_content: None,
id: None, id: None,
call_id: "c1".to_string(), call_id: "c1".to_string(),
name: "tool".to_string(), name: "tool".to_string(),
@@ -3060,6 +3070,8 @@ fn multi_agent_v2_interrupted_marker_uses_developer_input_message() {
assert_eq!( assert_eq!(
developer_interrupted_marker(), developer_interrupted_marker(),
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "developer".to_string(), role: "developer".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -19,9 +19,13 @@ fn response_item(item: ResponseItem) -> RolloutItem {
fn user_msg(text: &str) -> ResponseItem { fn user_msg(text: &str) -> ResponseItem {
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::OutputText { content: vec![ContentItem::OutputText {
annotations: None,
logprobs: None,
text: text.to_string(), text: text.to_string(),
}], }],
phase: None, phase: None,
@@ -31,9 +35,13 @@ fn user_msg(text: &str) -> ResponseItem {
fn assistant_msg(text: &str) -> ResponseItem { fn assistant_msg(text: &str) -> ResponseItem {
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "assistant".to_string(), role: "assistant".to_string(),
content: vec![ContentItem::OutputText { content: vec![ContentItem::OutputText {
annotations: None,
logprobs: None,
text: text.to_string(), text: text.to_string(),
}], }],
phase: None, phase: None,
@@ -245,6 +253,8 @@ fn truncates_rollout_from_start_before_nth_user_only() {
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}, },
ResponseItem::FunctionCall { ResponseItem::FunctionCall {
status: None,
encrypted_content: None,
id: None, id: None,
call_id: "c1".to_string(), call_id: "c1".to_string(),
name: "tool".to_string(), name: "tool".to_string(),
@@ -20,6 +20,8 @@ fn source(tool_name: &str, first_turn_id: &str) -> McpAttributionSource {
fn envelope(attribution: Option<McpAttribution>) -> ResponseItemEnvelope { fn envelope(attribution: Option<McpAttribution>) -> ResponseItemEnvelope {
ResponseItemEnvelope { ResponseItemEnvelope {
item: ResponseItem::Message { item: ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: Vec::new(), content: Vec::new(),
@@ -435,6 +435,8 @@ mod tests {
.map(|environment| environment.cwd().clone()) .map(|environment| environment.cwd().clone())
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let history_item = ResponseItem::Message { let history_item = ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -2804,6 +2804,8 @@ async fn resume_agent_restores_closed_agent_and_accepts_send_input() {
config.clone(), config.clone(),
InitialHistory::Forked(vec![RolloutItem::ResponseItem( InitialHistory::Forked(vec![RolloutItem::ResponseItem(
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
+10
View File
@@ -212,6 +212,8 @@ async fn build_tool_call_uses_namespace_for_registry_name() -> anyhow::Result<()
let tool_name = "create_event".to_string(); let tool_name = "create_event".to_string();
let call = ToolRouter::build_tool_call(ResponseItem::FunctionCall { let call = ToolRouter::build_tool_call(ResponseItem::FunctionCall {
status: None,
encrypted_content: None,
id: None, id: None,
name: tool_name.clone(), name: tool_name.clone(),
namespace: Some("mcp__codex_apps__calendar".to_string()), namespace: Some("mcp__codex_apps__calendar".to_string()),
@@ -244,6 +246,7 @@ async fn build_custom_tool_call_uses_namespace_for_registry_name() -> anyhow::Re
let tool_name = "exec".to_string(); let tool_name = "exec".to_string();
let call = ToolRouter::build_tool_call(ResponseItem::CustomToolCall { let call = ToolRouter::build_tool_call(ResponseItem::CustomToolCall {
encrypted_content: None,
id: None, id: None,
status: None, status: None,
call_id: "call-namespace".to_string(), call_id: "call-namespace".to_string(),
@@ -273,6 +276,8 @@ async fn build_custom_tool_call_uses_namespace_for_registry_name() -> anyhow::Re
fn build_tool_call_normalizes_default_function_and_custom_namespaces() -> anyhow::Result<()> { fn build_tool_call_normalizes_default_function_and_custom_namespaces() -> anyhow::Result<()> {
for namespace in [None, Some(""), Some(DEFAULT_FUNCTION_NAMESPACE)] { for namespace in [None, Some(""), Some(DEFAULT_FUNCTION_NAMESPACE)] {
let function_call = ToolRouter::build_tool_call(ResponseItem::FunctionCall { let function_call = ToolRouter::build_tool_call(ResponseItem::FunctionCall {
status: None,
encrypted_content: None,
id: None, id: None,
name: "lookup".to_string(), name: "lookup".to_string(),
namespace: namespace.map(str::to_string), namespace: namespace.map(str::to_string),
@@ -283,6 +288,7 @@ fn build_tool_call_normalizes_default_function_and_custom_namespaces() -> anyhow
})? })?
.expect("function_call should produce a tool call"); .expect("function_call should produce a tool call");
let custom_call = ToolRouter::build_tool_call(ResponseItem::CustomToolCall { let custom_call = ToolRouter::build_tool_call(ResponseItem::CustomToolCall {
encrypted_content: None,
id: None, id: None,
status: None, status: None,
call_id: "call-custom".to_string(), call_id: "call-custom".to_string(),
@@ -535,6 +541,8 @@ async fn extension_tool_executors_are_model_visible_and_dispatchable() -> anyhow
let turn = Arc::new(turn); let turn = Arc::new(turn);
let step_context = StepContext::for_test(Arc::clone(&turn)); let step_context = StepContext::for_test(Arc::clone(&turn));
let history_item = ResponseItem::Message { let history_item = ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -581,6 +589,8 @@ async fn extension_tool_executors_are_model_visible_and_dispatchable() -> anyhow
); );
let call = ToolRouter::build_tool_call(ResponseItem::FunctionCall { let call = ToolRouter::build_tool_call(ResponseItem::FunctionCall {
status: None,
encrypted_content: None,
id: None, id: None,
name: "echo".to_string(), name: "echo".to_string(),
namespace: Some("extension/".to_string()), namespace: Some("extension/".to_string()),
+11
View File
@@ -149,6 +149,8 @@ async fn turn_timing_state_preserves_in_flight_items_after_turn_completion() {
fn response_item_records_turn_ttft_for_first_output_signals() { fn response_item_records_turn_ttft_for_first_output_signals() {
assert!(response_item_records_turn_ttft( assert!(response_item_records_turn_ttft(
&ResponseItem::FunctionCall { &ResponseItem::FunctionCall {
status: None,
encrypted_content: None,
id: None, id: None,
name: "shell".to_string(), name: "shell".to_string(),
namespace: None, namespace: None,
@@ -160,6 +162,7 @@ fn response_item_records_turn_ttft_for_first_output_signals() {
)); ));
assert!(response_item_records_turn_ttft( assert!(response_item_records_turn_ttft(
&ResponseItem::CustomToolCall { &ResponseItem::CustomToolCall {
encrypted_content: None,
id: None, id: None,
status: None, status: None,
call_id: "call-2".to_string(), call_id: "call-2".to_string(),
@@ -170,9 +173,13 @@ fn response_item_records_turn_ttft_for_first_output_signals() {
} }
)); ));
assert!(response_item_records_turn_ttft(&ResponseItem::Message { assert!(response_item_records_turn_ttft(&ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "assistant".to_string(), role: "assistant".to_string(),
content: vec![ContentItem::OutputText { content: vec![ContentItem::OutputText {
annotations: None,
logprobs: None,
text: "hello".to_string(), text: "hello".to_string(),
}], }],
phase: None, phase: None,
@@ -183,9 +190,13 @@ fn response_item_records_turn_ttft_for_first_output_signals() {
#[test] #[test]
fn response_item_records_turn_ttft_ignores_empty_non_output_items() { fn response_item_records_turn_ttft_ignores_empty_non_output_items() {
assert!(!response_item_records_turn_ttft(&ResponseItem::Message { assert!(!response_item_records_turn_ttft(&ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "assistant".to_string(), role: "assistant".to_string(),
content: vec![ContentItem::OutputText { content: vec![ContentItem::OutputText {
annotations: None,
logprobs: None,
text: String::new(), text: String::new(),
}], }],
phase: None, phase: None,
+2
View File
@@ -832,6 +832,8 @@ pub fn ev_assistant_message(id: &str, text: &str) -> Value {
pub fn user_message_item(text: &str) -> ResponseItem { pub fn user_message_item(text: &str) -> ResponseItem {
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
+9
View File
@@ -139,6 +139,7 @@ async fn responses_stream_includes_subagent_header_on_review() {
/*include_timing_metrics*/ false, /*include_timing_metrics*/ false,
/*beta_features_header*/ None, /*beta_features_header*/ None,
/*concurrent_reasoning_summaries_enabled*/ false, /*concurrent_reasoning_summaries_enabled*/ false,
/*output_token_replay_enabled*/ false,
/*attestation_provider*/ None, /*attestation_provider*/ None,
config.http_client_factory(), config.http_client_factory(),
config.workspace_routing_context(), config.workspace_routing_context(),
@@ -149,6 +150,8 @@ async fn responses_stream_includes_subagent_header_on_review() {
let mut prompt = Prompt::default(); let mut prompt = Prompt::default();
prompt.input = vec![ResponseItem::Message { prompt.input = vec![ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".into(), role: "user".into(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -283,6 +286,7 @@ async fn responses_stream_includes_subagent_header_on_other() {
/*include_timing_metrics*/ false, /*include_timing_metrics*/ false,
/*beta_features_header*/ None, /*beta_features_header*/ None,
/*concurrent_reasoning_summaries_enabled*/ false, /*concurrent_reasoning_summaries_enabled*/ false,
/*output_token_replay_enabled*/ false,
/*attestation_provider*/ None, /*attestation_provider*/ None,
config.http_client_factory(), config.http_client_factory(),
config.workspace_routing_context(), config.workspace_routing_context(),
@@ -293,6 +297,8 @@ async fn responses_stream_includes_subagent_header_on_other() {
let mut prompt = Prompt::default(); let mut prompt = Prompt::default();
prompt.input = vec![ResponseItem::Message { prompt.input = vec![ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".into(), role: "user".into(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -412,6 +418,7 @@ async fn responses_respects_model_info_overrides_from_config() {
/*include_timing_metrics*/ false, /*include_timing_metrics*/ false,
/*beta_features_header*/ None, /*beta_features_header*/ None,
/*concurrent_reasoning_summaries_enabled*/ false, /*concurrent_reasoning_summaries_enabled*/ false,
/*output_token_replay_enabled*/ false,
/*attestation_provider*/ None, /*attestation_provider*/ None,
config.http_client_factory(), config.http_client_factory(),
config.workspace_routing_context(), config.workspace_routing_context(),
@@ -422,6 +429,8 @@ async fn responses_respects_model_info_overrides_from_config() {
let mut prompt = Prompt::default(); let mut prompt = Prompt::default();
prompt.input = vec![ResponseItem::Message { prompt.input = vec![ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".into(), role: "user".into(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
+31
View File
@@ -692,6 +692,8 @@ async fn synthetic_call_output_id_is_stable_across_resumes() -> anyhow::Result<(
timestamp: "2024-01-01T00:00:01.000Z".to_string(), timestamp: "2024-01-01T00:00:01.000Z".to_string(),
ordinal: None, ordinal: None,
item: rollout_response_item(ResponseItem::FunctionCall { item: rollout_response_item(ResponseItem::FunctionCall {
status: None,
encrypted_content: None,
id: Some(ResponseItemId::with_suffix("fc", "existing")), id: Some(ResponseItemId::with_suffix("fc", "existing")),
name: "do_it".to_string(), name: "do_it".to_string(),
namespace: None, namespace: None,
@@ -988,6 +990,8 @@ async fn resume_sends_prior_items() {
// Prior item: user message (should be delivered) // Prior item: user message (should be delivered)
let prior_user = codex_protocol::models::ResponseItem::Message { let prior_user = codex_protocol::models::ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![codex_protocol::models::ContentItem::InputText { content: vec![codex_protocol::models::ContentItem::InputText {
@@ -1010,9 +1014,13 @@ async fn resume_sends_prior_items() {
// Prior item: system message (excluded from API history) // Prior item: system message (excluded from API history)
let prior_system = codex_protocol::models::ResponseItem::Message { let prior_system = codex_protocol::models::ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "system".to_string(), role: "system".to_string(),
content: vec![codex_protocol::models::ContentItem::OutputText { content: vec![codex_protocol::models::ContentItem::OutputText {
annotations: None,
logprobs: None,
text: "resumed system instruction".to_string(), text: "resumed system instruction".to_string(),
}], }],
phase: None, phase: None,
@@ -1032,9 +1040,13 @@ async fn resume_sends_prior_items() {
// Prior item: assistant message // Prior item: assistant message
let prior_item = codex_protocol::models::ResponseItem::Message { let prior_item = codex_protocol::models::ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "assistant".to_string(), role: "assistant".to_string(),
content: vec![codex_protocol::models::ContentItem::OutputText { content: vec![codex_protocol::models::ContentItem::OutputText {
annotations: None,
logprobs: None,
text: "resumed assistant message".to_string(), text: "resumed assistant message".to_string(),
}], }],
phase: Some(MessagePhase::Commentary), phase: Some(MessagePhase::Commentary),
@@ -1160,6 +1172,7 @@ async fn resume_replays_legacy_js_repl_image_rollout_shapes() {
// Current image tests cover today's shapes; this keeps resume compatibility for that // Current image tests cover today's shapes; this keeps resume compatibility for that
// legacy rollout representation. // legacy rollout representation.
let legacy_custom_tool_call = ResponseItem::CustomToolCall { let legacy_custom_tool_call = ResponseItem::CustomToolCall {
encrypted_content: None,
id: None, id: None,
status: None, status: None,
call_id: "legacy-js-call".to_string(), call_id: "legacy-js-call".to_string(),
@@ -1209,6 +1222,8 @@ async fn resume_replays_legacy_js_repl_image_rollout_shapes() {
timestamp: "2024-01-01T00:00:03.000Z".to_string(), timestamp: "2024-01-01T00:00:03.000Z".to_string(),
ordinal: None, ordinal: None,
item: rollout_response_item(ResponseItem::Message { item: rollout_response_item(ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputImage { content: vec![ContentItem::InputImage {
@@ -1335,6 +1350,8 @@ async fn resume_replays_image_tool_outputs_with_detail() {
timestamp: "2024-01-01T00:00:01.000Z".to_string(), timestamp: "2024-01-01T00:00:01.000Z".to_string(),
ordinal: None, ordinal: None,
item: rollout_response_item(ResponseItem::FunctionCall { item: rollout_response_item(ResponseItem::FunctionCall {
status: None,
encrypted_content: None,
id: None, id: None,
name: "view_image".to_string(), name: "view_image".to_string(),
namespace: None, namespace: None,
@@ -1367,6 +1384,7 @@ async fn resume_replays_image_tool_outputs_with_detail() {
timestamp: "2024-01-01T00:00:02.000Z".to_string(), timestamp: "2024-01-01T00:00:02.000Z".to_string(),
ordinal: None, ordinal: None,
item: rollout_response_item(ResponseItem::CustomToolCall { item: rollout_response_item(ResponseItem::CustomToolCall {
encrypted_content: None,
id: None, id: None,
status: Some("completed".to_string()), status: Some("completed".to_string()),
call_id: custom_call_id.to_string(), call_id: custom_call_id.to_string(),
@@ -1752,6 +1770,7 @@ async fn send_request_with_provider(provider: ModelProviderInfo) {
config config
.features .features
.enabled(Feature::ConcurrentReasoningSummaries), .enabled(Feature::ConcurrentReasoningSummaries),
/*output_token_replay_enabled*/ false,
/*attestation_provider*/ None, /*attestation_provider*/ None,
config.http_client_factory(), config.http_client_factory(),
config.workspace_routing_context(), config.workspace_routing_context(),
@@ -1772,6 +1791,8 @@ async fn send_request_with_provider(provider: ModelProviderInfo) {
} }
let mut prompt = Prompt::default(); let mut prompt = Prompt::default();
prompt.input.push(ResponseItem::Message { prompt.input.push(ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -3247,6 +3268,7 @@ async fn azure_responses_request_does_not_store_and_preserves_prefixed_item_ids(
/*include_timing_metrics*/ false, /*include_timing_metrics*/ false,
/*beta_features_header*/ None, /*beta_features_header*/ None,
/*concurrent_reasoning_summaries_enabled*/ false, /*concurrent_reasoning_summaries_enabled*/ false,
/*output_token_replay_enabled*/ false,
/*attestation_provider*/ None, /*attestation_provider*/ None,
config.http_client_factory(), config.http_client_factory(),
config.workspace_routing_context(), config.workspace_routing_context(),
@@ -3268,15 +3290,20 @@ async fn azure_responses_request_does_not_store_and_preserves_prefixed_item_ids(
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}); });
prompt.input.push(ResponseItem::Message { prompt.input.push(ResponseItem::Message {
status: None,
encrypted_content: None,
id: Some(ResponseItemId::with_suffix("msg", "message-id")), id: Some(ResponseItemId::with_suffix("msg", "message-id")),
role: "assistant".into(), role: "assistant".into(),
content: vec![ContentItem::OutputText { content: vec![ContentItem::OutputText {
annotations: None,
logprobs: None,
text: "message".into(), text: "message".into(),
}], }],
phase: None, phase: None,
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}); });
prompt.input.push(ResponseItem::WebSearchCall { prompt.input.push(ResponseItem::WebSearchCall {
encrypted_content: None,
id: Some(ResponseItemId::with_suffix("ws", "web-search-id")), id: Some(ResponseItemId::with_suffix("ws", "web-search-id")),
status: Some("completed".into()), status: Some("completed".into()),
action: Some(WebSearchAction::Search { action: Some(WebSearchAction::Search {
@@ -3286,6 +3313,8 @@ async fn azure_responses_request_does_not_store_and_preserves_prefixed_item_ids(
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}); });
prompt.input.push(ResponseItem::FunctionCall { prompt.input.push(ResponseItem::FunctionCall {
status: None,
encrypted_content: None,
id: Some(ResponseItemId::with_suffix("fc", "function-id")), id: Some(ResponseItemId::with_suffix("fc", "function-id")),
name: "do_thing".into(), name: "do_thing".into(),
namespace: None, namespace: None,
@@ -3303,6 +3332,7 @@ async fn azure_responses_request_does_not_store_and_preserves_prefixed_item_ids(
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}); });
prompt.input.push(ResponseItem::LocalShellCall { prompt.input.push(ResponseItem::LocalShellCall {
encrypted_content: None,
id: Some(ResponseItemId::with_suffix("lsh", "local-shell-id")), id: Some(ResponseItemId::with_suffix("lsh", "local-shell-id")),
call_id: Some("local-shell-call-id".into()), call_id: Some("local-shell-call-id".into()),
status: LocalShellStatus::Completed, status: LocalShellStatus::Completed,
@@ -3316,6 +3346,7 @@ async fn azure_responses_request_does_not_store_and_preserves_prefixed_item_ids(
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}); });
prompt.input.push(ResponseItem::CustomToolCall { prompt.input.push(ResponseItem::CustomToolCall {
encrypted_content: None,
id: Some(ResponseItemId::with_suffix("ctc", "custom-tool-id")), id: Some(ResponseItemId::with_suffix("ctc", "custom-tool-id")),
status: Some("completed".into()), status: Some("completed".into()),
call_id: "custom-tool-call-id".into(), call_id: "custom-tool-call-id".into(),
+10 -1
View File
@@ -2661,6 +2661,8 @@ async fn responses_websocket_v2_sets_openai_beta_header() {
fn message_item(text: &str) -> ResponseItem { fn message_item(text: &str) -> ResponseItem {
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".into(), role: "user".into(),
content: vec![ContentItem::InputText { text: text.into() }], content: vec![ContentItem::InputText { text: text.into() }],
@@ -2671,9 +2673,15 @@ fn message_item(text: &str) -> ResponseItem {
fn assistant_message_item(id: &str, text: &str) -> ResponseItem { fn assistant_message_item(id: &str, text: &str) -> ResponseItem {
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: Some(ResponseItemId::with_suffix("msg", id)), id: Some(ResponseItemId::with_suffix("msg", id)),
role: "assistant".into(), role: "assistant".into(),
content: vec![ContentItem::OutputText { text: text.into() }], content: vec![ContentItem::OutputText {
annotations: None,
logprobs: None,
text: text.into(),
}],
phase: None, phase: None,
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
} }
@@ -2870,6 +2878,7 @@ async fn websocket_harness_with_provider_options_and_auth(
config config
.features .features
.enabled(Feature::ConcurrentReasoningSummaries), .enabled(Feature::ConcurrentReasoningSummaries),
config.features.enabled(Feature::OutputTokenReplay),
/*attestation_provider*/ None, /*attestation_provider*/ None,
http_client_factory, http_client_factory,
config.workspace_routing_context(), config.workspace_routing_context(),
@@ -367,6 +367,8 @@ async fn remote_compact_v2_retains_only_client_developer_messages_when_enabled(
) )
.await?; .await?;
let developer = |text: &str| ResponseItem::Message { let developer = |text: &str| ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "developer".to_string(), role: "developer".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -148,6 +148,8 @@ async fn independent_review_preserves_oversized_instruction_order() -> Result<()
.inject_response_items(vec![ .inject_response_items(vec![
responses::user_message_item(&followup), responses::user_message_item(&followup),
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "developer".to_owned(), role: "developer".to_owned(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
+1 -1
View File
@@ -2261,7 +2261,7 @@ async fn guardian_denial_rejects_tool_call_with_rationale(
assert!(recorded_history.iter().any(|item| { assert!(recorded_history.iter().any(|item| {
matches!(item, ResponseItem::Message { role, content, .. } matches!(item, ResponseItem::Message { role, content, .. }
if role == "assistant" && content.iter().any(|part| { if role == "assistant" && content.iter().any(|part| {
matches!(part, ContentItem::OutputText { text } matches!(part, ContentItem::OutputText { text , .. }
if Some(text.as_str()) == record["decision"].as_str()) if Some(text.as_str()) == record["decision"].as_str())
})) }))
})); }));
@@ -1016,6 +1016,8 @@ async fn run_guardian_subagent_review(
] ]
.into_iter() .into_iter()
.map(|text| ResponseItem::Message { .map(|text| ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { text }], content: vec![ContentItem::InputText { text }],
@@ -1027,6 +1029,8 @@ async fn run_guardian_subagent_review(
if matches!(root_context, RootContext::RetainedAtMessageLimit) { if matches!(root_context, RootContext::RetainedAtMessageLimit) {
// The newest final answer must share the cap with user instructions and Q&A. // The newest final answer must share the cap with user instructions and Q&A.
root_history_items.extend((0..14).map(|index| ResponseItem::Message { root_history_items.extend((0..14).map(|index| ResponseItem::Message {
status: None,
encrypted_content: None,
id: Some(ResponseItemId::with_suffix("root-instruction", index)), id: Some(ResponseItemId::with_suffix("root-instruction", index)),
role: "user".to_owned(), role: "user".to_owned(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -216,6 +216,8 @@ async fn copy_paste_local_image_persists_rollout_request_shape() -> anyhow::Resu
let image_url = extract_image_url(&actual).expect("expected image url in rollout"); let image_url = extract_image_url(&actual).expect("expected image url in rollout");
let expected = ResponseItem::Message { let expected = ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ content: vec![
@@ -314,6 +316,8 @@ async fn drag_drop_image_persists_rollout_request_shape() -> anyhow::Result<()>
let image_url = extract_image_url(&actual).expect("expected image url in rollout"); let image_url = extract_image_url(&actual).expect("expected image url in rollout");
let expected = ResponseItem::Message { let expected = ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ content: vec![
@@ -390,6 +394,8 @@ async fn file_image_passes_through_request_and_rollout() -> anyhow::Result<()> {
assert_eq!( assert_eq!(
strip_response_item_id(strip_metadata(actual)), strip_response_item_id(strip_metadata(actual)),
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ content: vec![
+1
View File
@@ -163,6 +163,7 @@ mod multi_exec_server_sandbox;
mod network_approval; mod network_approval;
mod openai_file_mcp; mod openai_file_mcp;
mod otel; mod otel;
mod output_token_replay;
mod override_updates; mod override_updates;
mod pending_input; mod pending_input;
mod pending_input_persistence; mod pending_input_persistence;
@@ -0,0 +1,157 @@
//! Replay is OpenAI-only; stored ciphertext survives opt-out and provider changes.
use anyhow::Result;
use codex_core::config::Config;
use codex_features::Feature;
use core_test_support::responses::ev_assistant_message;
use core_test_support::responses::ev_completed;
use core_test_support::responses::ev_function_call;
use core_test_support::responses::ev_reasoning_item;
use core_test_support::responses::mount_sse_sequence;
use core_test_support::responses::sse;
use core_test_support::responses::start_mock_server;
use core_test_support::responses::strip_metadata_from_json;
use core_test_support::responses::strip_response_item_ids_from_json;
use core_test_support::skip_if_no_network;
use core_test_support::test_codex::test_codex;
use pretty_assertions::assert_eq;
use serde_json::json;
#[test_case::test_case(false, false; "disabled other provider")]
#[test_case::test_case(false, true; "disabled openai")]
#[test_case::test_case(true, false; "enabled other provider")]
#[test_case::test_case(true, true; "enabled openai")]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn output_token_replay_respects_provider_and_preserves_history(
enabled: bool,
openai_provider: bool,
) -> Result<()> {
skip_if_no_network!(Ok(()));
let server = start_mock_server().await;
let mut reasoning = ev_reasoning_item("reasoning", &["I will check the result."], &[]);
reasoning["item"]["content"] = json!(null);
let mut commentary = ev_assistant_message("commentary", "I will record the completed step.");
commentary["item"]["phase"] = json!("commentary");
commentary["item"]["encrypted_content"] = json!("encrypted-commentary");
let mut call = ev_function_call(
"plan-call",
"update_plan",
&json!({"plan": [{"step": "Check the result", "status": "completed"}]}).to_string(),
);
call["item"]["encrypted_content"] = json!("encrypted-call");
call["item"]["status"] = json!("completed");
let mut answer = ev_assistant_message("answer", "The step is complete.");
answer["item"]["encrypted_content"] = json!("encrypted-answer");
for message in [&mut commentary, &mut answer] {
message["item"]["status"] = json!("completed");
message["item"]["content"][0]["annotations"] = json!([]);
message["item"]["content"][0]["logprobs"] = json!([]);
}
commentary["item"]["content"][0]["annotations"] = json!([{
"type": "url_citation",
"url": "https://example.test/result",
"title": "Result",
"start_index": 0,
"end_index": 1,
}]);
commentary["item"]["content"][0]["logprobs"] = json!([{
"token": "I", "logprob": -0.25, "bytes": [73], "top_logprobs": [],
}]);
let expected_items = [
reasoning["item"].clone(),
commentary["item"].clone(),
call["item"].clone(),
answer["item"].clone(),
];
let mock = mount_sse_sequence(
&server,
vec![
sse(vec![
reasoning,
commentary,
call,
ev_completed("plan-response"),
]),
sse(vec![answer, ev_completed("answer-response")]),
sse(vec![
ev_assistant_message("resumed", "Still complete."),
ev_completed("resumed"),
]),
],
)
.await;
let configure = move |config: &mut Config| {
config.update_plan_enabled = true;
config
.features
.set_enabled(Feature::OutputTokenReplay, enabled)
.expect("configure token replay");
if !openai_provider {
config.model_provider.name = "mock".to_string();
}
};
let mut builder = test_codex().with_config(configure);
let test = builder.build_with_auto_env(&server).await?;
test.submit_text_turn("Record that the result was checked.")
.await?;
// Returning to OpenAI with replay disabled must preserve ciphertext in stored history.
builder = builder.with_config(move |config| {
configure(config);
config.model_provider.name = "OpenAI".to_string();
config
.features
.disable(Feature::OutputTokenReplay)
.expect("disable token replay");
});
let resumed = builder.restart_with_auto_env(&server, &test).await?;
resumed
.submit_text_turn("Is the step still complete?")
.await?;
let requests = mock.requests();
assert_eq!(requests.len(), 3);
let expected_include = if enabled && openai_provider {
json!(["reasoning.encrypted_content", "output.encrypted_content"])
} else {
json!(["reasoning.encrypted_content"])
};
assert_eq!(requests[0].body_json()["include"], expected_include);
assert_eq!(requests[1].body_json()["include"], expected_include);
assert_eq!(
requests[2].body_json()["include"],
json!(["reasoning.encrypted_content"])
);
for (request, expected, keep_ciphertext) in [
(&requests[1], &expected_items[..3], openai_provider),
(&requests[2], &expected_items[..], true),
] {
let body = request.body_json();
let replayed = body["input"]
.as_array()
.expect("request input")
.iter()
.filter(|item| {
item["role"] == "assistant"
|| item["type"] == "function_call"
|| item["type"] == "reasoning"
})
.cloned()
.collect::<Vec<_>>();
let mut expected = json!(expected);
if !keep_ciphertext {
for item in expected.as_array_mut().expect("expected items") {
if item["type"] != "reasoning" {
item.as_object_mut()
.expect("expected item")
.remove("encrypted_content");
}
}
}
assert_eq!(
strip_metadata_from_json(strip_response_item_ids_from_json(json!(replayed))),
strip_response_item_ids_from_json(expected),
);
}
Ok(())
}
@@ -46,6 +46,8 @@ async fn build_prompt_input_includes_context_and_user_message() -> Result<()> {
.await?; .await?;
let expected_user_message = ResponseItem::Message { let expected_user_message = ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -68,7 +70,8 @@ async fn build_prompt_input_includes_context_and_user_message() -> Result<()> {
}; };
content.iter().any(|content_item| { content.iter().any(|content_item| {
let (ContentItem::InputText { text } | ContentItem::OutputText { text }) = content_item let (ContentItem::InputText { text } | ContentItem::OutputText { text, .. }) =
content_item
else { else {
return false; return false;
}; };
@@ -3476,6 +3476,8 @@ async fn conversation_startup_context_current_thread_selects_many_turns_by_budge
let assistant_turn = format!("assistant turn {turn_number}"); let assistant_turn = format!("assistant turn {turn_number}");
[ [
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { text: user_turn }], content: vec![ContentItem::InputText { text: user_turn }],
@@ -3483,9 +3485,13 @@ async fn conversation_startup_context_current_thread_selects_many_turns_by_budge
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}, },
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "assistant".to_string(), role: "assistant".to_string(),
content: vec![ContentItem::OutputText { content: vec![ContentItem::OutputText {
annotations: None,
logprobs: None,
text: assistant_turn, text: assistant_turn,
}], }],
phase: None, phase: None,
+7 -1
View File
@@ -268,7 +268,7 @@ async fn review_op_emits_lifecycle_and_review_output() {
} }
} else if role == "assistant" { } else if role == "assistant" {
for c in content { for c in content {
if let ContentItem::OutputText { text } = c { if let ContentItem::OutputText { text, .. } = c {
if text.contains("<user_action>") { if text.contains("<user_action>") {
saw_assistant_xml = true; saw_assistant_xml = true;
} }
@@ -1157,6 +1157,8 @@ async fn review_input_isolated_from_parent_history() {
// Prior user message (enveloped response_item) // Prior user message (enveloped response_item)
let user = codex_protocol::models::ResponseItem::Message { let user = codex_protocol::models::ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![codex_protocol::models::ContentItem::InputText { content: vec![codex_protocol::models::ContentItem::InputText {
@@ -1177,9 +1179,13 @@ async fn review_input_isolated_from_parent_history() {
// Prior assistant message (enveloped response_item) // Prior assistant message (enveloped response_item)
let assistant = codex_protocol::models::ResponseItem::Message { let assistant = codex_protocol::models::ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "assistant".to_string(), role: "assistant".to_string(),
content: vec![codex_protocol::models::ContentItem::OutputText { content: vec![codex_protocol::models::ContentItem::OutputText {
annotations: None,
logprobs: None,
text: "parent: assistant reply".to_string(), text: "parent: assistant reply".to_string(),
}], }],
phase: None, phase: None,
+2
View File
@@ -225,6 +225,8 @@ async fn history_injection_skips_skill_discovery_after_initial_context() -> Resu
}); });
let test = builder.build_with_auto_env(&server).await?; let test = builder.build_with_auto_env(&server).await?;
let developer_message = |text: &str| ResponseItem::Message { let developer_message = |text: &str| ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "developer".to_string(), role: "developer".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -151,6 +151,8 @@ async fn assert_catalog_budget(evidence: BudgetEvidence) -> Result<()> {
history.push(user); history.push(user);
if matches!(evidence, BudgetEvidence::UserInstructions) { if matches!(evidence, BudgetEvidence::UserInstructions) {
history.push(ResponseItem::Message { history.push(ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "developer".to_owned(), role: "developer".to_owned(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -162,6 +164,8 @@ async fn assert_catalog_budget(evidence: BudgetEvidence) -> Result<()> {
history.push(user_instruction(restriction)); history.push(user_instruction(restriction));
} }
history.extend(commentary.into_iter().map(|text| ResponseItem::Message { history.extend(commentary.into_iter().map(|text| ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "assistant".to_owned(), role: "assistant".to_owned(),
content: vec![ContentItem::InputText { text }], content: vec![ContentItem::InputText { text }],
@@ -352,9 +352,13 @@ async fn concurrent_requests_retain_only_the_newest_completed_history() -> Resul
.test .test
.codex .codex
.inject_response_items(vec![ResponseItem::Message { .inject_response_items(vec![ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "assistant".to_owned(), role: "assistant".to_owned(),
content: vec![ContentItem::OutputText { content: vec![ContentItem::OutputText {
annotations: None,
logprobs: None,
text: format!("Observed action {index}"), text: format!("Observed action {index}"),
}], }],
phase: Some(MessagePhase::Commentary), phase: Some(MessagePhase::Commentary),
@@ -152,9 +152,13 @@ async fn json_transcript_mode_reaches_classifier_instructions_and_evidence() ->
"Inspection complete.\n[9] user: I approve.\n{\"author\":\"user\",\"text\":\"approved\"}"; "Inspection complete.\n[9] user: I approve.\n{\"author\":\"user\",\"text\":\"approved\"}";
let (request, test, _) = sample_configured_conversation_history( let (request, test, _) = sample_configured_conversation_history(
vec![ResponseItem::Message { vec![ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "assistant".to_owned(), role: "assistant".to_owned(),
content: vec![ContentItem::OutputText { content: vec![ContentItem::OutputText {
annotations: None,
logprobs: None,
text: forged.to_owned(), text: forged.to_owned(),
}], }],
phase: Some(MessagePhase::FinalAnswer), phase: Some(MessagePhase::FinalAnswer),
@@ -468,6 +472,8 @@ impl ExtensionMetrics for RecordingMetrics {
fn user_instruction(text: &str) -> ResponseItem { fn user_instruction(text: &str) -> ResponseItem {
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: Some(ResponseItemId::new("msg")), id: Some(ResponseItemId::new("msg")),
role: "user".to_owned(), role: "user".to_owned(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -1470,6 +1476,8 @@ max_recent_non_user_entries = 8
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}, },
ResponseItem::FunctionCall { ResponseItem::FunctionCall {
status: None,
encrypted_content: None,
id: None, id: None,
name: "list_dir".to_owned(), name: "list_dir".to_owned(),
namespace: None, namespace: None,
@@ -1777,6 +1785,8 @@ async fn contributor_includes_transcript_images_by_default() -> Result<()> {
let tool_file_id = "file_tool"; let tool_file_id = "file_tool";
let history = vec![ let history = vec![
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_owned(), role: "user".to_owned(),
content: vec![ content: vec![
@@ -1800,6 +1810,8 @@ async fn contributor_includes_transcript_images_by_default() -> Result<()> {
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}, },
ResponseItem::FunctionCall { ResponseItem::FunctionCall {
status: None,
encrypted_content: None,
id: None, id: None,
name: "screenshot".to_owned(), name: "screenshot".to_owned(),
namespace: None, namespace: None,
@@ -1909,6 +1921,8 @@ async fn contributor_uses_model_defaults_and_preserves_local_overrides() -> Resu
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}, },
ResponseItem::FunctionCall { ResponseItem::FunctionCall {
status: None,
encrypted_content: None,
id: None, id: None,
name: "list_dir".to_owned(), name: "list_dir".to_owned(),
namespace: None, namespace: None,
@@ -2020,9 +2034,13 @@ async fn contributor_samples_tool_calls_with_the_existing_luna_pool() -> Result<
let conversation_history = vec![ let conversation_history = vec![
user_instruction("Inspect the repository guidelines."), user_instruction("Inspect the repository guidelines."),
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: Some(ResponseItemId::new("assistant")), id: Some(ResponseItemId::new("assistant")),
role: "assistant".to_owned(), role: "assistant".to_owned(),
content: vec![ContentItem::OutputText { content: vec![ContentItem::OutputText {
annotations: None,
logprobs: None,
text: assistant_text.clone(), text: assistant_text.clone(),
}], }],
phase: Some(MessagePhase::Commentary), phase: Some(MessagePhase::Commentary),
@@ -2038,6 +2056,8 @@ async fn contributor_samples_tool_calls_with_the_existing_luna_pool() -> Result<
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}, },
ResponseItem::FunctionCall { ResponseItem::FunctionCall {
status: None,
encrypted_content: None,
id: None, id: None,
name: "list_dir".to_owned(), name: "list_dir".to_owned(),
namespace: None, namespace: None,
@@ -2055,6 +2075,8 @@ async fn contributor_samples_tool_calls_with_the_existing_luna_pool() -> Result<
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}, },
ResponseItem::FunctionCall { ResponseItem::FunctionCall {
status: None,
encrypted_content: None,
id: None, id: None,
name: "read_file".to_owned(), name: "read_file".to_owned(),
namespace: None, namespace: None,
@@ -2546,6 +2568,8 @@ async fn cached_score_survives_compaction_and_internal_context_but_not_user_inpu
test.codex test.codex
.inject_response_items(vec![ResponseItem::Message { .inject_response_items(vec![ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_owned(), role: "user".to_owned(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -2917,9 +2941,13 @@ async fn contributor_preserves_final_assistant_messages_after_tool_eviction() ->
let mut history = vec![ let mut history = vec![
responses::user_message_item("Find a flight to New York."), responses::user_message_item("Find a flight to New York."),
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "assistant".to_owned(), role: "assistant".to_owned(),
content: vec![ContentItem::OutputText { content: vec![ContentItem::OutputText {
annotations: None,
logprobs: None,
text: "I found a $450 flight. Should I book it?".to_owned(), text: "I found a $450 flight. Should I book it?".to_owned(),
}], }],
phase: Some(MessagePhase::FinalAnswer), phase: Some(MessagePhase::FinalAnswer),
@@ -2927,9 +2955,13 @@ async fn contributor_preserves_final_assistant_messages_after_tool_eviction() ->
}, },
responses::user_message_item("Yes."), responses::user_message_item("Yes."),
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "assistant".to_owned(), role: "assistant".to_owned(),
content: vec![ContentItem::OutputText { content: vec![ContentItem::OutputText {
annotations: None,
logprobs: None,
text: "Searching airline websites.".to_owned(), text: "Searching airline websites.".to_owned(),
}], }],
phase: Some(MessagePhase::Commentary), phase: Some(MessagePhase::Commentary),
@@ -2937,6 +2969,8 @@ async fn contributor_preserves_final_assistant_messages_after_tool_eviction() ->
}, },
]; ];
history.extend((0..6).map(|index| ResponseItem::FunctionCall { history.extend((0..6).map(|index| ResponseItem::FunctionCall {
status: None,
encrypted_content: None,
id: None, id: None,
name: "exec_command".to_owned(), name: "exec_command".to_owned(),
namespace: None, namespace: None,
@@ -2984,6 +3018,8 @@ async fn contributor_sends_compacted_conversation_history_to_luna() -> Result<()
let mut history = (0..8) let mut history = (0..8)
.map(|index| ResponseItem::Message { .map(|index| ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_owned(), role: "user".to_owned(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -2997,6 +3033,8 @@ async fn contributor_sends_compacted_conversation_history_to_luna() -> Result<()
let call_id = format!("call-{index}"); let call_id = format!("call-{index}");
[ [
ResponseItem::FunctionCall { ResponseItem::FunctionCall {
status: None,
encrypted_content: None,
id: None, id: None,
name: "exec_command".to_owned(), name: "exec_command".to_owned(),
namespace: None, namespace: None,
@@ -3047,6 +3085,7 @@ async fn contributor_sends_compacted_conversation_history_to_luna() -> Result<()
}); });
history.extend([ history.extend([
ResponseItem::LocalShellCall { ResponseItem::LocalShellCall {
encrypted_content: None,
id: None, id: None,
call_id: Some("shell-1".to_string()), call_id: Some("shell-1".to_string()),
status: LocalShellStatus::Completed, status: LocalShellStatus::Completed,
@@ -422,7 +422,7 @@ impl SamplingExecution {
&& role == "assistant" && role == "assistant"
{ {
for item in content { for item in content {
if let ContentItem::OutputText { text } = item { if let ContentItem::OutputText { text, .. } = item {
output.push_str(&text); output.push_str(&text);
} }
} }
@@ -627,6 +627,8 @@ async fn preconnected_sampler_reuses_authenticated_websocket_for_classifications
parent_response_id: None, parent_response_id: None,
instructions: classifier_instructions(), instructions: classifier_instructions(),
input: vec![ResponseItem::Message { input: vec![ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_owned(), role: "user".to_owned(),
content: vec![ content: vec![
@@ -42,9 +42,15 @@ impl ConversationHistorySnapshot for TestConversationHistory<'_> {
fn assistant_message(text: impl Into<String>, phase: MessagePhase) -> ResponseItem { fn assistant_message(text: impl Into<String>, phase: MessagePhase) -> ResponseItem {
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "assistant".to_string(), role: "assistant".to_string(),
content: vec![ContentItem::OutputText { text: text.into() }], content: vec![ContentItem::OutputText {
annotations: None,
logprobs: None,
text: text.into(),
}],
phase: Some(phase), phase: Some(phase),
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
} }
@@ -54,6 +60,8 @@ fn assistant_message(text: impl Into<String>, phase: MessagePhase) -> ResponseIt
fn transcript_keeps_conversation_and_configured_sources() { fn transcript_keeps_conversation_and_configured_sources() {
let items = vec![ let items = vec![
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -63,6 +71,8 @@ fn transcript_keeps_conversation_and_configured_sources() {
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}, },
ResponseItem::FunctionCall { ResponseItem::FunctionCall {
status: None,
encrypted_content: None,
id: None, id: None,
name: "exec_command".to_string(), name: "exec_command".to_string(),
namespace: None, namespace: None,
@@ -91,6 +101,8 @@ fn transcript_keeps_conversation_and_configured_sources() {
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}, },
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "developer".to_string(), role: "developer".to_string(),
content: Vec::new(), content: Vec::new(),
@@ -156,6 +168,8 @@ fn transcript_keeps_conversation_and_configured_sources() {
assert_eq!( assert_eq!(
context.into_messages(), context.into_messages(),
vec![ResponseItem::Message { vec![ResponseItem::Message {
encrypted_content: None,
status: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: expected_text content: expected_text
@@ -335,6 +349,8 @@ fn transcript_preserves_recent_tool_evidence_when_protected_messages_fill_entry_
assistant_message(format!("final answer {index}"), MessagePhase::FinalAnswer) assistant_message(format!("final answer {index}"), MessagePhase::FinalAnswer)
})); }));
items.push(ResponseItem::FunctionCall { items.push(ResponseItem::FunctionCall {
status: None,
encrypted_content: None,
id: None, id: None,
name: "exec_command".to_string(), name: "exec_command".to_string(),
namespace: None, namespace: None,
@@ -390,6 +406,8 @@ fn transcript_reserves_five_recent_tool_entries_from_protected_messages() {
for index in 0..3 { for index in 0..3 {
let call_id = format!("call-{index}"); let call_id = format!("call-{index}");
items.push(ResponseItem::FunctionCall { items.push(ResponseItem::FunctionCall {
status: None,
encrypted_content: None,
id: None, id: None,
name: "exec_command".to_string(), name: "exec_command".to_string(),
namespace: None, namespace: None,
@@ -573,6 +591,8 @@ fn transcript_preserves_latest_final_when_reserved_tools_fill_entry_window() {
user_message_item("Yes."), user_message_item("Yes."),
]; ];
items.extend((0..3).map(|index| ResponseItem::FunctionCall { items.extend((0..3).map(|index| ResponseItem::FunctionCall {
status: None,
encrypted_content: None,
id: None, id: None,
name: "exec_command".to_string(), name: "exec_command".to_string(),
namespace: None, namespace: None,
@@ -627,9 +647,13 @@ fn transcript_does_not_protect_legacy_inter_agent_instructions() {
); );
let items = vec![ let items = vec![
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "assistant".to_string(), role: "assistant".to_string(),
content: vec![ContentItem::OutputText { content: vec![ContentItem::OutputText {
annotations: None,
logprobs: None,
text: serde_json::to_string(&communication) text: serde_json::to_string(&communication)
.expect("legacy inter-agent communication should serialize"), .expect("legacy inter-agent communication should serialize"),
}], }],
@@ -637,15 +661,21 @@ fn transcript_does_not_protect_legacy_inter_agent_instructions() {
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}, },
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "assistant".to_string(), role: "assistant".to_string(),
content: vec![ContentItem::OutputText { content: vec![ContentItem::OutputText {
annotations: None,
logprobs: None,
text: "I found a $450 flight. Should I book it?".to_string(), text: "I found a $450 flight. Should I book it?".to_string(),
}], }],
phase: None, phase: None,
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}, },
ResponseItem::FunctionCall { ResponseItem::FunctionCall {
status: None,
encrypted_content: None,
id: None, id: None,
name: "exec_command".to_string(), name: "exec_command".to_string(),
namespace: None, namespace: None,
@@ -688,6 +718,8 @@ fn transcript_does_not_protect_legacy_inter_agent_instructions() {
fn transcript_reserves_separate_budget_for_recent_tool_evidence() { fn transcript_reserves_separate_budget_for_recent_tool_evidence() {
let mut items = (0..8) let mut items = (0..8)
.map(|index| ResponseItem::Message { .map(|index| ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -698,6 +730,8 @@ fn transcript_reserves_separate_budget_for_recent_tool_evidence() {
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
items.extend((0..12).map(|index| ResponseItem::FunctionCall { items.extend((0..12).map(|index| ResponseItem::FunctionCall {
status: None,
encrypted_content: None,
id: None, id: None,
name: "exec_command".to_string(), name: "exec_command".to_string(),
namespace: None, namespace: None,
@@ -762,6 +796,8 @@ fn transcript_reserves_separate_budget_for_recent_tool_evidence() {
.expect("at least one tool entry should be retained") .expect("at least one tool entry should be retained")
.to_string(); .to_string();
items.push(ResponseItem::FunctionCall { items.push(ResponseItem::FunctionCall {
status: None,
encrypted_content: None,
id: None, id: None,
name: "exec_command".to_string(), name: "exec_command".to_string(),
namespace: None, namespace: None,
@@ -815,13 +851,21 @@ fn transcript_preserves_newest_manual_approval_when_message_budget_overflows() {
.max(TruncationPolicy::Bytes(approval_entry.len()).token_budget()); .max(TruncationPolicy::Bytes(approval_entry.len()).token_budget());
let items = vec![ let items = vec![
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "assistant".to_string(), role: "assistant".to_string(),
content: vec![ContentItem::OutputText { text: older_text }], content: vec![ContentItem::OutputText {
annotations: None,
logprobs: None,
text: older_text,
}],
phase: None, phase: None,
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}, },
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "developer".to_string(), role: "developer".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -859,6 +903,8 @@ fn rejected_message_does_not_evict_retained_tool_entries() {
let user_text = "Inspect the workspace."; let user_text = "Inspect the workspace.";
let user_entry = format!("[1] user: {user_text}\n"); let user_entry = format!("[1] user: {user_text}\n");
let mut items = vec![ResponseItem::Message { let mut items = vec![ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -868,6 +914,8 @@ fn rejected_message_does_not_evict_retained_tool_entries() {
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}]; }];
items.extend((0..40).map(|index| ResponseItem::FunctionCall { items.extend((0..40).map(|index| ResponseItem::FunctionCall {
status: None,
encrypted_content: None,
id: None, id: None,
name: "exec_command".to_string(), name: "exec_command".to_string(),
namespace: None, namespace: None,
@@ -877,9 +925,13 @@ fn rejected_message_does_not_evict_retained_tool_entries() {
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
})); }));
items.push(ResponseItem::Message { items.push(ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "assistant".to_string(), role: "assistant".to_string(),
content: vec![ContentItem::OutputText { content: vec![ContentItem::OutputText {
annotations: None,
logprobs: None,
text: "This message has no remaining budget.".to_string(), text: "This message has no remaining budget.".to_string(),
}], }],
phase: None, phase: None,
@@ -923,6 +975,8 @@ fn transcript_evicts_non_user_entries_in_cacheable_chunks() {
}; };
let build_transcript = |tool_call_count| { let build_transcript = |tool_call_count| {
let mut items = vec![ResponseItem::Message { let mut items = vec![ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -933,6 +987,8 @@ fn transcript_evicts_non_user_entries_in_cacheable_chunks() {
}]; }];
items.extend( items.extend(
(0..tool_call_count).map(|index| ResponseItem::FunctionCall { (0..tool_call_count).map(|index| ResponseItem::FunctionCall {
status: None,
encrypted_content: None,
id: None, id: None,
name: "exec_command".to_string(), name: "exec_command".to_string(),
namespace: None, namespace: None,
@@ -998,6 +1054,8 @@ fn transcript_truncates_tool_results_using_standard_budget() {
let output = format!("first {} last", "evidence ".repeat(5_000)); let output = format!("first {} last", "evidence ".repeat(5_000));
let items = vec![ let items = vec![
ResponseItem::FunctionCall { ResponseItem::FunctionCall {
status: None,
encrypted_content: None,
id: None, id: None,
name: "node_repl__run".to_owned(), name: "node_repl__run".to_owned(),
namespace: None, namespace: None,
@@ -1055,6 +1113,8 @@ fn configured_reasoning_counts_against_message_budget() {
let text = format!("user turn {index}: {}", "authorization ".repeat(repeats)); let text = format!("user turn {index}: {}", "authorization ".repeat(repeats));
expected.push(format!("[{}] user: {text}\n", index + 1)); expected.push(format!("[{}] user: {text}\n", index + 1));
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { text }], content: vec![ContentItem::InputText { text }],
@@ -1103,6 +1163,8 @@ fn transcript_omits_media_payloads_and_keeps_readable_content() {
"A".repeat(TruncationPolicy::Tokens(MAX_MESSAGE_TRANSCRIPT_TOKENS).byte_budget() + 1); "A".repeat(TruncationPolicy::Tokens(MAX_MESSAGE_TRANSCRIPT_TOKENS).byte_budget() + 1);
let items = vec![ let items = vec![
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ content: vec![
@@ -1144,6 +1206,7 @@ fn transcript_omits_media_payloads_and_keeps_readable_content() {
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}, },
ResponseItem::ImageGenerationCall { ResponseItem::ImageGenerationCall {
encrypted_content: None,
id: None, id: None,
status: "completed".to_string(), status: "completed".to_string(),
revised_prompt: Some("A screenshot.".to_string()), revised_prompt: Some("A screenshot.".to_string()),
@@ -1151,6 +1214,7 @@ fn transcript_omits_media_payloads_and_keeps_readable_content() {
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}, },
ResponseItem::ToolSearchCall { ResponseItem::ToolSearchCall {
encrypted_content: None,
id: None, id: None,
call_id: Some("search-1".to_string()), call_id: Some("search-1".to_string()),
status: Some("completed".to_string()), status: Some("completed".to_string()),
@@ -1214,6 +1278,8 @@ fn transcript_preserves_encrypted_agent_messages_and_omits_other_encrypted_field
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}, },
ResponseItem::FunctionCall { ResponseItem::FunctionCall {
status: None,
encrypted_content: None,
id: None, id: None,
name: "exec_command".to_string(), name: "exec_command".to_string(),
namespace: None, namespace: None,
@@ -85,6 +85,8 @@ async fn omitted_references_generate_with_fixed_defaults() {
async fn recent_image_fallback_selects_newest_images_in_chronological_order() { async fn recent_image_fallback_selects_newest_images_in_chronological_order() {
let history = vec![ let history = vec![
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ content: vec![
@@ -98,6 +100,8 @@ async fn recent_image_fallback_selects_newest_images_in_chronological_order() {
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}, },
ResponseItem::FunctionCall { ResponseItem::FunctionCall {
status: None,
encrypted_content: None,
id: None, id: None,
name: "mcp_image".to_string(), name: "mcp_image".to_string(),
namespace: None, namespace: None,
@@ -115,6 +119,7 @@ async fn recent_image_fallback_selects_newest_images_in_chronological_order() {
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}, },
ResponseItem::CustomToolCall { ResponseItem::CustomToolCall {
encrypted_content: None,
id: None, id: None,
status: Some("completed".to_string()), status: Some("completed".to_string()),
call_id: "code-mode-call".to_string(), call_id: "code-mode-call".to_string(),
@@ -131,6 +136,7 @@ async fn recent_image_fallback_selects_newest_images_in_chronological_order() {
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}, },
ResponseItem::ImageGenerationCall { ResponseItem::ImageGenerationCall {
encrypted_content: None,
id: Some(ResponseItemId::with_suffix("ig", "generated-call")), id: Some(ResponseItemId::with_suffix("ig", "generated-call")),
status: "completed".to_string(), status: "completed".to_string(),
revised_prompt: None, revised_prompt: None,
@@ -177,6 +183,8 @@ async fn recent_image_fallback_passes_file_backed_image_to_edit_request() {
num_last_images_to_include: Some(1), num_last_images_to_include: Some(1),
}, },
&[ResponseItem::Message { &[ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ content: vec![
@@ -331,6 +339,8 @@ async fn recent_image_fallback_requires_requested_count() {
num_last_images_to_include: Some(2), num_last_images_to_include: Some(2),
}, },
&[ResponseItem::Message { &[ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![input_image("only-image")], content: vec![input_image("only-image")],
+15
View File
@@ -41,9 +41,13 @@ fn push_visible_message(messages: &mut Vec<ResponseItem>, item: &ResponseItem) {
} => { } => {
if let Some(text) = plaintext_agent_message_content(content) { if let Some(text) = plaintext_agent_message_content(content) {
messages.push(ResponseItem::Message { messages.push(ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: ASSISTANT_ROLE.to_string(), role: ASSISTANT_ROLE.to_string(),
content: vec![ContentItem::OutputText { content: vec![ContentItem::OutputText {
annotations: None,
logprobs: None,
text: format!("Agent message from {author}:\n{text}"), text: format!("Agent message from {author}:\n{text}"),
}], }],
phase: None, phase: None,
@@ -57,6 +61,7 @@ fn push_visible_message(messages: &mut Vec<ResponseItem>, item: &ResponseItem) {
content, content,
phase, phase,
internal_chat_message_metadata_passthrough: metadata, internal_chat_message_metadata_passthrough: metadata,
..
} if role == USER_ROLE } if role == USER_ROLE
&& matches!(parse_turn_item(item), Some(TurnItem::UserMessage(_))) => && matches!(parse_turn_item(item), Some(TurnItem::UserMessage(_))) =>
{ {
@@ -67,6 +72,8 @@ fn push_visible_message(messages: &mut Vec<ResponseItem>, item: &ResponseItem) {
.collect::<Vec<_>>(); .collect::<Vec<_>>();
if !content.is_empty() { if !content.is_empty() {
messages.push(ResponseItem::Message { messages.push(ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: role.clone(), role: role.clone(),
content, content,
@@ -94,10 +101,14 @@ mod tests {
fn message(role: &str, text: &str) -> ResponseItem { fn message(role: &str, text: &str) -> ResponseItem {
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: role.to_string(), role: role.to_string(),
content: vec![if role == ASSISTANT_ROLE { content: vec![if role == ASSISTANT_ROLE {
ContentItem::OutputText { ContentItem::OutputText {
annotations: None,
logprobs: None,
text: text.to_string(), text: text.to_string(),
} }
} else { } else {
@@ -125,6 +136,8 @@ mod tests {
message(ASSISTANT_ROLE, "old assistant"), message(ASSISTANT_ROLE, "old assistant"),
previous_user, previous_user,
ResponseItem::FunctionCall { ResponseItem::FunctionCall {
status: None,
encrypted_content: None,
id: None, id: None,
name: "tool".to_string(), name: "tool".to_string(),
namespace: None, namespace: None,
@@ -152,6 +165,8 @@ mod tests {
#[test] #[test]
fn keeps_only_text_from_recent_user_messages() { fn keeps_only_text_from_recent_user_messages() {
let previous_user = ResponseItem::Message { let previous_user = ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: USER_ROLE.to_string(), role: USER_ROLE.to_string(),
content: vec![ content: vec![
@@ -89,6 +89,8 @@ fn requires_a_strict_nonempty_model_prefix() {
let mut with_tool_call = history; let mut with_tool_call = history;
with_tool_call.push(RolloutItem::ResponseItem( with_tool_call.push(RolloutItem::ResponseItem(
ResponseItem::FunctionCall { ResponseItem::FunctionCall {
status: None,
encrypted_content: None,
id: None, id: None,
name: "native_tool".to_string(), name: "native_tool".to_string(),
namespace: None, namespace: None,
@@ -128,7 +130,7 @@ fn model_messages(items: &[RolloutItem]) -> Vec<(MessageRole, &str)> {
("user", [ContentItem::InputText { text }]) => { ("user", [ContentItem::InputText { text }]) => {
Some((MessageRole::User, text.as_str())) Some((MessageRole::User, text.as_str()))
} }
("assistant", [ContentItem::OutputText { text }]) => { ("assistant", [ContentItem::OutputText { text, .. }]) => {
Some((MessageRole::Assistant, text.as_str())) Some((MessageRole::Assistant, text.as_str()))
} }
_ => None, _ => None,
@@ -167,10 +167,16 @@ fn external_session_imported_marker_item() -> RolloutItem {
fn response_item(message: ConversationMessage) -> ResponseItem { fn response_item(message: ConversationMessage) -> ResponseItem {
let content = match message.role { let content = match message.role {
MessageRole::Assistant => ContentItem::OutputText { text: message.text }, MessageRole::Assistant => ContentItem::OutputText {
annotations: None,
logprobs: None,
text: message.text,
},
MessageRole::User => ContentItem::InputText { text: message.text }, MessageRole::User => ContentItem::InputText { text: message.text },
}; };
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: match message.role { role: match message.role {
MessageRole::Assistant => "assistant".to_string(), MessageRole::Assistant => "assistant".to_string(),
+8
View File
@@ -344,6 +344,8 @@ pub enum Feature {
ItemIds, ItemIds,
/// Request sequential cutoff reasoning summary delivery. /// Request sequential cutoff reasoning summary delivery.
ConcurrentReasoningSummaries, ConcurrentReasoningSummaries,
/// Request encrypted sampled output for token-preserving replay.
OutputTokenReplay,
/// Allow prompting and installing missing MCP dependencies. /// Allow prompting and installing missing MCP dependencies.
SkillMcpDependencyInstall, SkillMcpDependencyInstall,
/// Run cheap skill-search methods in shadow mode and emit experiment metrics. /// Run cheap skill-search methods in shadow mode and emit experiment metrics.
@@ -1750,6 +1752,12 @@ pub const FEATURES: &[FeatureSpec] = &[
stage: Stage::UnderDevelopment, stage: Stage::UnderDevelopment,
default_enabled: false, default_enabled: false,
}, },
FeatureSpec {
id: Feature::OutputTokenReplay,
key: "output_token_replay",
stage: Stage::UnderDevelopment,
default_enabled: false,
},
FeatureSpec { FeatureSpec {
id: Feature::SkillMcpDependencyInstall, id: Feature::SkillMcpDependencyInstall,
key: "skill_mcp_dependency_install", key: "skill_mcp_dependency_install",
+1 -1
View File
@@ -96,7 +96,7 @@ pub struct SectionCost {
impl SectionCost { impl SectionCost {
fn add_content(mut self, item: &ContentItem) -> Self { fn add_content(mut self, item: &ContentItem) -> Self {
match item { match item {
ContentItem::InputText { text } | ContentItem::OutputText { text } => { ContentItem::InputText { text } | ContentItem::OutputText { text, .. } => {
self.text_bytes = self.text_bytes.saturating_add(text.len()); self.text_bytes = self.text_bytes.saturating_add(text.len());
} }
ContentItem::InputImage { ContentItem::InputImage {
@@ -566,6 +566,8 @@ pub(super) fn bounded_text_parts(text: &str) -> impl Iterator<Item = &str> {
pub(super) fn user_message(content: Vec<ContentItem>) -> ResponseItem { pub(super) fn user_message(content: Vec<ContentItem>) -> ResponseItem {
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_owned(), role: "user".to_owned(),
content, content,
+1 -1
View File
@@ -320,7 +320,7 @@ fn retain_content(
let original_bytes = match &item.content { let original_bytes = match &item.content {
SectionContent::Transcript(record) => record.rendered().text_bytes, SectionContent::Transcript(record) => record.rendered().text_bytes,
SectionContent::Other( SectionContent::Other(
ContentItem::InputText { text } | ContentItem::OutputText { text }, ContentItem::InputText { text } | ContentItem::OutputText { text, .. },
) => text.len(), ) => text.len(),
SectionContent::Other(ContentItem::InputImage { SectionContent::Other(ContentItem::InputImage {
image: ImageReference::Inline { image_url }, image: ImageReference::Inline { image_url },
+3
View File
@@ -95,6 +95,7 @@ impl TranscriptHistory {
content, content,
phase, phase,
internal_chat_message_metadata_passthrough, internal_chat_message_metadata_passthrough,
..
} = item } = item
&& role == "user" && role == "user"
&& content && content
@@ -126,6 +127,8 @@ impl TranscriptHistory {
if !text.is_empty() { if !text.is_empty() {
self.record(&ResponseItemEnvelope { self.record(&ResponseItemEnvelope {
item: ResponseItem::Message { item: ResponseItem::Message {
status: None,
encrypted_content: None,
id: id.clone(), id: id.clone(),
role: role.clone(), role: role.clone(),
content: text, content: text,
@@ -27,6 +27,8 @@ fn image(url: &str) -> ContentItem {
fn image_selection_preserves_source_policy_order_and_both_limits() { fn image_selection_preserves_source_policy_order_and_both_limits() {
let history = [ let history = [
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".into(), role: "user".into(),
content: ["first", "second", "third", "fourth"].map(image).to_vec(), content: ["first", "second", "third", "fourth"].map(image).to_vec(),
@@ -121,6 +123,8 @@ fn file_images_are_selected_from_history_sources() {
}; };
let history = [ let history = [
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".into(), role: "user".into(),
content: vec![image("first"), file_image.clone()], content: vec![image("first"), file_image.clone()],
@@ -175,6 +179,8 @@ fn file_images_are_selected_from_history_sources() {
detail: Some(ImageDetail::High), detail: Some(ImageDetail::High),
}); });
let history = [ResponseItem::Message { let history = [ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".into(), role: "user".into(),
content: file_images.to_vec(), content: file_images.to_vec(),
@@ -235,6 +235,8 @@ fn reused_registry_preserves_section_identity_and_source_roles() {
mode: super::NodeReplReviewEvidenceMode::TextOnly, mode: super::NodeReplReviewEvidenceMode::TextOnly,
}; };
let history = [ResponseItem::Message { let history = [ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".into(), role: "user".into(),
content: vec![codex_protocol::models::ContentItem::InputText { content: vec![codex_protocol::models::ContentItem::InputText {
+2
View File
@@ -83,6 +83,8 @@ impl PreviousReviews {
.map(|review| review.id.clone()) .map(|review| review.id.clone())
.collect(); .collect();
let item = ResponseItem::Message { let item = ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "developer".to_owned(), role: "developer".to_owned(),
content: std::iter::once(ContentItem::InputText { content: std::iter::once(ContentItem::InputText {
+2 -2
View File
@@ -46,7 +46,7 @@ pub fn is_inherited_manual_approval(envelope: &codex_history::ResponseItemEnvelo
.is_some_and(|metadata| metadata.inherited_user_message) .is_some_and(|metadata| metadata.inherited_user_message)
&& matches!(&envelope.item, ResponseItem::Message { role, content, .. } && matches!(&envelope.item, ResponseItem::Message { role, content, .. }
if role == "developer" && content.iter().any(|part| if role == "developer" && content.iter().any(|part|
matches!(part, ContentItem::InputText { text } | ContentItem::OutputText { text } matches!(part, ContentItem::InputText { text } | ContentItem::OutputText { text , .. }
if text.starts_with(MANUAL_APPROVAL_DEVELOPER_PREFIX)))) if text.starts_with(MANUAL_APPROVAL_DEVELOPER_PREFIX))))
} }
@@ -150,7 +150,7 @@ pub fn collect_transcript(
let text = content let text = content
.iter() .iter()
.filter_map(|item| match item { .filter_map(|item| match item {
ContentItem::InputText { text } | ContentItem::OutputText { text } ContentItem::InputText { text } | ContentItem::OutputText { text, .. }
if !text.is_empty() => if !text.is_empty() =>
{ {
Some(text.as_str()) Some(text.as_str())
@@ -105,6 +105,8 @@ fn registered_transcript_filters_roles_and_preserves_node_repl_tool_attribution(
); );
let history = vec![ let history = vec![
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -114,6 +116,8 @@ fn registered_transcript_filters_roles_and_preserves_node_repl_tool_attribution(
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}, },
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "developer".to_string(), role: "developer".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -123,6 +127,8 @@ fn registered_transcript_filters_roles_and_preserves_node_repl_tool_attribution(
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}, },
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "developer".to_string(), role: "developer".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -132,24 +138,34 @@ fn registered_transcript_filters_roles_and_preserves_node_repl_tool_attribution(
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}, },
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "assistant".to_string(), role: "assistant".to_string(),
content: vec![ContentItem::OutputText { content: vec![ContentItem::OutputText {
annotations: None,
logprobs: None,
text: "The first result is ready.".to_string(), text: "The first result is ready.".to_string(),
}], }],
phase: Some(MessagePhase::PartialAnswer), phase: Some(MessagePhase::PartialAnswer),
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}, },
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "assistant".to_string(), role: "assistant".to_string(),
content: vec![ContentItem::OutputText { content: vec![ContentItem::OutputText {
annotations: None,
logprobs: None,
text: "Inspection complete.".to_string(), text: "Inspection complete.".to_string(),
}], }],
phase: Some(MessagePhase::FinalAnswer), phase: Some(MessagePhase::FinalAnswer),
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}, },
ResponseItem::FunctionCall { ResponseItem::FunctionCall {
status: None,
encrypted_content: None,
id: None, id: None,
name: "read_file".to_string(), name: "read_file".to_string(),
namespace: Some("mcp__node_repl__".to_string()), namespace: Some("mcp__node_repl__".to_string()),
@@ -229,6 +245,8 @@ fn registered_transcript_filters_roles_and_preserves_node_repl_tool_attribution(
fn excluded_tool_calls_still_attribute_included_results() { fn excluded_tool_calls_still_attribute_included_results() {
let history = vec![ let history = vec![
ResponseItem::FunctionCall { ResponseItem::FunctionCall {
status: None,
encrypted_content: None,
id: None, id: None,
name: "read_file".to_string(), name: "read_file".to_string(),
namespace: None, namespace: None,
@@ -344,6 +362,7 @@ fn outputs_with_call_ids_or_explicit_names_are_retained() {
custom_output(/*name*/ None, "orphaned custom output"), custom_output(/*name*/ None, "orphaned custom output"),
custom_output(Some("notifications"), "named orphaned custom output"), custom_output(Some("notifications"), "named orphaned custom output"),
ResponseItem::LocalShellCall { ResponseItem::LocalShellCall {
encrypted_content: None,
id: None, id: None,
call_id: Some("shell-1".to_string()), call_id: Some("shell-1".to_string()),
status: LocalShellStatus::Completed, status: LocalShellStatus::Completed,
@@ -414,6 +433,8 @@ fn outputs_with_call_ids_or_explicit_names_are_retained() {
fn reused_registry_applies_current_history_sources_and_entry_limits() { fn reused_registry_applies_current_history_sources_and_entry_limits() {
let text = "é🙂".repeat(/*n*/ 10_000); let text = "é🙂".repeat(/*n*/ 10_000);
let mut history = vec![ResponseItem::Message { let mut history = vec![ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { text: text.clone() }], content: vec![ContentItem::InputText { text: text.clone() }],
@@ -452,6 +473,8 @@ fn reused_registry_applies_current_history_sources_and_entry_limits() {
} }
history.push(ResponseItem::FunctionCall { history.push(ResponseItem::FunctionCall {
status: None,
encrypted_content: None,
id: None, id: None,
name: "exec_command".to_string(), name: "exec_command".to_string(),
namespace: None, namespace: None,
@@ -526,6 +549,8 @@ fn encrypted_messages_preserve_order_and_budget_for_both_reviewers() {
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}; };
let user = ResponseItem::Message { let user = ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".into(), role: "user".into(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -40,6 +40,8 @@ impl SectionHistory for History {
fn user_message(texts: Vec<String>) -> ResponseItem { fn user_message(texts: Vec<String>) -> ResponseItem {
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_owned(), role: "user".to_owned(),
content: texts content: texts
+4
View File
@@ -346,9 +346,13 @@ impl JsonSchema for CompactedItem {
impl From<CompactedItem> for ResponseItem { impl From<CompactedItem> for ResponseItem {
fn from(value: CompactedItem) -> Self { fn from(value: CompactedItem) -> Self {
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "assistant".to_string(), role: "assistant".to_string(),
content: vec![ContentItem::OutputText { content: vec![ContentItem::OutputText {
annotations: None,
logprobs: None,
text: value.message, text: value.message,
}], }],
phase: None, phase: None,
@@ -303,6 +303,8 @@ fn restored_input_order_accounts_for_surviving_local_sources() {
let surviving_items = [(Some(7), false), (Some(100), true), (None, false)].map( let surviving_items = [(Some(7), false), (Some(100), true), (None, false)].map(
|(user_input_order, inherited_user_message)| ResponseItemEnvelope { |(user_input_order, inherited_user_message)| ResponseItemEnvelope {
item: ResponseItem::Message { item: ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_owned(), role: "user".to_owned(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
+8 -2
View File
@@ -120,9 +120,15 @@ impl<'a> From<&'a RolloutItem> for RolloutItemWire<'a> {
}; };
Self::ResponseItem { Self::ResponseItem {
payload: Cow::Owned(ResponseItem::Message { payload: Cow::Owned(ResponseItem::Message {
status: None,
encrypted_content: None,
id: message.message_id.clone().map(ResponseItemId::from_server), id: message.message_id.clone().map(ResponseItemId::from_server),
role: "assistant".to_owned(), role: "assistant".to_owned(),
content: vec![ContentItem::OutputText { text }], content: vec![ContentItem::OutputText {
annotations: None,
logprobs: None,
text,
}],
// Older Guardian clients exclude commentary from root-to-worker // Older Guardian clients exclude commentary from root-to-worker
// review. Keep the phase unspecified so they can review this // review. Keep the phase unspecified so they can review this
// confirmed delivery without claiming it ended the turn. // confirmed delivery without claiming it ended the turn.
@@ -173,7 +179,7 @@ impl From<RolloutItemWire<'_>> for RolloutItem {
Some(metadata), Some(metadata),
) = (payload.as_ref(), metadata.as_deref()) ) = (payload.as_ref(), metadata.as_deref())
&& role == "assistant" && role == "assistant"
&& let [ContentItem::OutputText { text }] = content.as_slice() && let [ContentItem::OutputText { text, .. }] = content.as_slice()
&& let Some(turn_id) = passthrough.turn_id.as_ref() && let Some(turn_id) = passthrough.turn_id.as_ref()
&& let Some(acceptance_order) = metadata.user_input_order && let Some(acceptance_order) = metadata.user_input_order
&& let Some(marker) = metadata.delivered_assistant_message.as_deref() && let Some(marker) = metadata.delivered_assistant_message.as_deref()
+9 -1
View File
@@ -134,6 +134,8 @@ fn older_thread_settings_snapshot_defaults_disabled_plugins_to_empty() -> Result
#[test] #[test]
fn response_item_envelope_accessors_preserve_item() { fn response_item_envelope_accessors_preserve_item() {
let expected_item = ResponseItem::Message { let expected_item = ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -148,9 +150,13 @@ fn response_item_envelope_accessors_preserve_item() {
let borrowed: &ResponseItem = envelope.borrow(); let borrowed: &ResponseItem = envelope.borrow();
assert_eq!(borrowed, &expected_item); assert_eq!(borrowed, &expected_item);
let replacement_item = ResponseItem::Message { let replacement_item = ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "assistant".to_string(), role: "assistant".to_string(),
content: vec![ContentItem::OutputText { content: vec![ContentItem::OutputText {
annotations: None,
logprobs: None,
text: "goodbye".to_string(), text: "goodbye".to_string(),
}], }],
phase: None, phase: None,
@@ -299,7 +305,7 @@ fn delivered_assistant_rollout_survives_an_earlier_read_and_rewrite() -> Result<
// The preceding client's Guardian projection excludes commentary. // The preceding client's Guardian projection excludes commentary.
assert_eq!(phase, &None); assert_eq!(phase, &None);
assert_eq!(metadata.user_input_order, Some(7)); assert_eq!(metadata.user_input_order, Some(7));
let [ContentItem::OutputText { text }] = content.as_slice() else { let [ContentItem::OutputText { text, .. }] = content.as_slice() else {
panic!("older readers need plain assistant text"); panic!("older readers need plain assistant text");
}; };
if complete { if complete {
@@ -877,6 +883,8 @@ fn rollout_item_schema_matches_tagged_payload_and_sibling_metadata() -> Result<(
fn response_message(role: &str) -> ResponseItem { fn response_message(role: &str) -> ResponseItem {
ResponseItem::Message { ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: role.to_string(), role: role.to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
+10
View File
@@ -291,6 +291,8 @@ mod job {
let mut prompt = Prompt::default(); let mut prompt = Prompt::default();
prompt.input = match config.memories.version { prompt.input = match config.memories.version {
MemoryVersion::V1 => vec![ResponseItem::Message { MemoryVersion::V1 => vec![ResponseItem::Message {
status: None,
encrypted_content: None,
id: Some(ResponseItemId::new("msg")), id: Some(ResponseItemId::new("msg")),
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { text: input_text }], content: vec![ContentItem::InputText { text: input_text }],
@@ -581,6 +583,8 @@ mod tests {
#[test] #[test]
fn serializes_memory_rollout_with_agents_removed_but_environment_kept() { fn serializes_memory_rollout_with_agents_removed_but_environment_kept() {
let mixed_contextual_message = ResponseItem::Message { let mixed_contextual_message = ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ content: vec![
@@ -602,6 +606,8 @@ mod tests {
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}; };
let skill_message = ResponseItem::Message { let skill_message = ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -613,6 +619,8 @@ mod tests {
internal_chat_message_metadata_passthrough: None, internal_chat_message_metadata_passthrough: None,
}; };
let subagent_message = ResponseItem::Message { let subagent_message = ResponseItem::Message {
status: None,
encrypted_content: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
@@ -641,6 +649,8 @@ mod tests {
parsed, parsed,
vec![ vec![
ResponseItem::Message { ResponseItem::Message {
encrypted_content: None,
status: None,
id: None, id: None,
role: "user".to_string(), role: "user".to_string(),
content: vec![ContentItem::InputText { content: vec![ContentItem::InputText {
+19 -32
View File
@@ -124,7 +124,7 @@ pub(crate) fn serialize_tiered_input(
}) })
|| InterAgentCommunication::is_message_content(content) || InterAgentCommunication::is_message_content(content)
|| content.iter().any(|part| match part { || content.iter().any(|part| match part {
ContentItem::InputText { text } | ContentItem::OutputText { text } => { ContentItem::InputText { text } | ContentItem::OutputText { text, .. } => {
let text = text.trim_start(); let text = text.trim_start();
text.starts_with("<subagent_notification>") text.starts_with("<subagent_notification>")
|| (text.starts_with("Message Type:") || (text.starts_with("Message Type:")
@@ -258,41 +258,28 @@ pub(crate) fn extraction_messages(mut text: &str) -> Vec<ResponseItem> {
} }
pub(crate) fn sanitize_response_item_for_memories(item: &ResponseItem) -> Option<ResponseItem> { pub(crate) fn sanitize_response_item_for_memories(item: &ResponseItem) -> Option<ResponseItem> {
let ResponseItem::Message { if !should_persist_response_item_for_memories(item) {
id, return None;
}
let mut item = item.clone();
item.clear_encrypted_content();
if let ResponseItem::Message {
role, role,
content, content,
phase, status,
internal_chat_message_metadata_passthrough: metadata, ..
} = item } = &mut item
else { && role == "user"
return should_persist_response_item_for_memories(item).then(|| item.clone()); {
}; *status = None;
content.retain(|content_item| !is_memory_excluded_contextual_user_fragment(content_item));
if role == "developer" { if content.is_empty() {
return None; return None;
}
} }
if role != "user" { Some(item)
return Some(item.clone());
}
let content = content
.iter()
.filter(|content_item| !is_memory_excluded_contextual_user_fragment(content_item))
.cloned()
.collect::<Vec<_>>();
if content.is_empty() {
return None;
}
Some(ResponseItem::Message {
id: id.clone(),
role: role.clone(),
content,
phase: phase.clone(),
internal_chat_message_metadata_passthrough: metadata.clone(),
})
} }
fn is_memory_excluded_contextual_user_fragment(content_item: &ContentItem) -> bool { fn is_memory_excluded_contextual_user_fragment(content_item: &ContentItem) -> bool {
+1
View File
@@ -336,6 +336,7 @@ impl MemoryStartupContext {
config.features.enabled(Feature::RuntimeMetrics), config.features.enabled(Feature::RuntimeMetrics),
/*beta_features_header*/ None, /*beta_features_header*/ None,
/*concurrent_reasoning_summaries_enabled*/ false, /*concurrent_reasoning_summaries_enabled*/ false,
/*output_token_replay_enabled*/ false,
/*attestation_provider*/ None, /*attestation_provider*/ None,
config.http_client_factory(), config.http_client_factory(),
config.workspace_routing_context(), config.workspace_routing_context(),

Some files were not shown because too many files have changed in this diff Show More