mirror of
https://github.com/openai/codex.git
synced 2026-10-28 18:58:46 +00:00
## 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
172 lines
5.6 KiB
Rust
172 lines
5.6 KiB
Rust
use codex_protocol::models::FunctionCallOutputBody;
|
|
use codex_protocol::models::FunctionCallOutputPayload;
|
|
use pretty_assertions::assert_eq;
|
|
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn preparation_canonicalizes_data_urls_and_rejects_remote_urls() {
|
|
let mut items = vec![ResponseItem::Message {
|
|
status: None,
|
|
encrypted_content: None,
|
|
id: None,
|
|
role: "user".to_string(),
|
|
content: vec![
|
|
ContentItem::InputAudio {
|
|
audio_url: "data:audio/x-wav;base64,YXVkaW8=".to_string(),
|
|
},
|
|
ContentItem::InputAudio {
|
|
audio_url: "data:audio/ogg;base64,YXVkaW8=".to_string(),
|
|
},
|
|
ContentItem::InputAudio {
|
|
audio_url: "https://example.com/audio.mp3".to_string(),
|
|
},
|
|
],
|
|
phase: None,
|
|
internal_chat_message_metadata_passthrough: None,
|
|
}];
|
|
|
|
prepare_response_items(&mut items);
|
|
|
|
assert_eq!(
|
|
items,
|
|
vec![ResponseItem::Message {
|
|
encrypted_content: None,
|
|
status: None,
|
|
id: None,
|
|
role: "user".to_string(),
|
|
content: vec![
|
|
ContentItem::InputAudio {
|
|
audio_url: "data:audio/wav;base64,YXVkaW8=".to_string(),
|
|
},
|
|
ContentItem::InputAudio {
|
|
audio_url: "data:audio/ogg;base64,YXVkaW8=".to_string(),
|
|
},
|
|
ContentItem::InputText {
|
|
text: "audio content omitted because it could not be processed".to_string(),
|
|
},
|
|
],
|
|
phase: None,
|
|
internal_chat_message_metadata_passthrough: None,
|
|
}]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn preparation_replaces_invalid_message_audio_with_placeholders() {
|
|
let mut items = vec![ResponseItem::Message {
|
|
status: None,
|
|
encrypted_content: None,
|
|
id: None,
|
|
role: "user".to_string(),
|
|
content: vec![
|
|
ContentItem::InputAudio {
|
|
audio_url: "data:audio/wav;base64,%%%".to_string(),
|
|
},
|
|
ContentItem::InputAudio {
|
|
audio_url: "data:audio/flac;base64,YXVkaW8=".to_string(),
|
|
},
|
|
],
|
|
phase: None,
|
|
internal_chat_message_metadata_passthrough: None,
|
|
}];
|
|
|
|
prepare_response_items(&mut items);
|
|
|
|
assert_eq!(
|
|
items,
|
|
vec![ResponseItem::Message {
|
|
encrypted_content: None,
|
|
status: None,
|
|
id: None,
|
|
role: "user".to_string(),
|
|
content: vec![
|
|
ContentItem::InputText {
|
|
text: "audio content omitted because it could not be processed".to_string(),
|
|
},
|
|
ContentItem::InputText {
|
|
text: "audio content omitted because its format is not supported; use wav, mp3, m4a, webm, or ogg".to_string(),
|
|
},
|
|
],
|
|
phase: None,
|
|
internal_chat_message_metadata_passthrough: None,
|
|
}]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn preparation_replaces_only_failed_tool_audio_and_preserves_metadata() {
|
|
let mut items = vec![ResponseItem::FunctionCallOutput {
|
|
id: None,
|
|
call_id: Some("call-1".to_string()),
|
|
name: None,
|
|
namespace: None,
|
|
output: FunctionCallOutputPayload {
|
|
body: FunctionCallOutputBody::ContentItems(vec![
|
|
FunctionCallOutputContentItem::InputText {
|
|
text: "before".to_string(),
|
|
},
|
|
FunctionCallOutputContentItem::InputAudio {
|
|
audio_url: "data:audio/wav;base64,YXVkaW8=".to_string(),
|
|
},
|
|
FunctionCallOutputContentItem::InputAudio {
|
|
audio_url: "data:audio/wav,not-base64".to_string(),
|
|
},
|
|
]),
|
|
success: Some(true),
|
|
},
|
|
internal_chat_message_metadata_passthrough: None,
|
|
}];
|
|
|
|
prepare_response_items(&mut items);
|
|
|
|
assert_eq!(
|
|
items,
|
|
vec![ResponseItem::FunctionCallOutput {
|
|
id: None,
|
|
call_id: Some("call-1".to_string()),
|
|
name: None,
|
|
namespace: None,
|
|
output: FunctionCallOutputPayload {
|
|
body: FunctionCallOutputBody::ContentItems(vec![
|
|
FunctionCallOutputContentItem::InputText {
|
|
text: "before".to_string(),
|
|
},
|
|
FunctionCallOutputContentItem::InputAudio {
|
|
audio_url: "data:audio/wav;base64,YXVkaW8=".to_string(),
|
|
},
|
|
FunctionCallOutputContentItem::InputText {
|
|
text: "audio content omitted because it could not be processed".to_string(),
|
|
},
|
|
]),
|
|
success: Some(true),
|
|
},
|
|
internal_chat_message_metadata_passthrough: None,
|
|
}]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn preparation_errors_map_to_expected_placeholders() {
|
|
let cases = [
|
|
(
|
|
AudioPreparationError::InvalidDataUrl {
|
|
reason: "details remain in logs",
|
|
},
|
|
"audio content omitted because it could not be processed",
|
|
),
|
|
(
|
|
AudioPreparationError::UnsupportedFormat,
|
|
"audio content omitted because its format is not supported; use wav, mp3, m4a, webm, or ogg",
|
|
),
|
|
(
|
|
AudioPreparationError::AudioTooLarge { size: usize::MAX },
|
|
"audio content omitted because it exceeded the supported size limit; use a smaller audio file",
|
|
),
|
|
];
|
|
|
|
for (error, expected) in cases {
|
|
assert_eq!(error.placeholder(), expected);
|
|
}
|
|
}
|