mirror of
https://github.com/openai/codex.git
synced 2026-11-01 19:33:35 +00:00
## Why Image files can become unavailable between turns. Model requests should reflect their current availability while preserving the original references in saved history. ## What changed - Add opt-in availability checks through `AttachmentStore::is_resolvable`, retaining references by default and on backend or access errors. - Check distinct file IDs concurrently for each prepared request and replace confirmed-missing images with `[image unavailable]` in messages and tool outputs. - Apply the same handling to compaction requests, with background image checks running outside the session lock without delaying foreground turn completion. ## Testing Add unit tests and multi-turn scenarios covering placeholders, preserved history and annotations, concurrent deduplicated checks, backend errors, and nonblocking background compaction. GitOrigin-RevId: ec98c7014c6e6b5063d87b943d2cd66a9ce7c1e3
85 lines
2.6 KiB
Rust
85 lines
2.6 KiB
Rust
use pretty_assertions::assert_eq;
|
|
|
|
use super::*;
|
|
|
|
/// Debug output redacts attachment bytes in requests and results.
|
|
#[test]
|
|
fn attachment_debug_output_redacts_bytes() {
|
|
let request = UploadRequest {
|
|
thread_id: "thread-1".to_string(),
|
|
ephemeral: false,
|
|
file_name: Some("image.png".to_string()),
|
|
tags: HashMap::new(),
|
|
data: b"secret".to_vec(),
|
|
};
|
|
let result = UploadResult::Inline {
|
|
bytes: b"secret".to_vec(),
|
|
};
|
|
|
|
assert_eq!(
|
|
(format!("{request:?}"), format!("{result:?}")),
|
|
(
|
|
r#"UploadRequest { thread_id: "thread-1", ephemeral: false, file_name: Some("image.png"), tags: {}, data: "<redacted>" }"#.to_string(),
|
|
r#"Inline { bytes: "<redacted>" }"#.to_string(),
|
|
)
|
|
);
|
|
}
|
|
|
|
/// Debug output redacts credential-bearing file URLs in resolved metadata.
|
|
#[test]
|
|
fn attachment_metadata_debug_output_redacts_file_url() {
|
|
let metadata = AttachmentMetadata {
|
|
file_url: Some("https://attachments.test/file?signed=secret".to_string()),
|
|
..AttachmentMetadata::default()
|
|
};
|
|
|
|
let debug = format!("{metadata:?}");
|
|
|
|
assert!(!debug.contains("signed=secret"));
|
|
assert!(debug.contains("<redacted>"));
|
|
}
|
|
|
|
/// The inline store preserves PNG and JPEG attachment bytes.
|
|
#[tokio::test]
|
|
async fn inline_store_preserves_image_bytes() {
|
|
let cases: [(&str, &[u8]); 2] = [
|
|
("image.png", b"\x89PNG\r\n\x1a\n"),
|
|
("image.jpg", b"\xff\xd8\xff\xe0JFIF\x00\xff\xd9"),
|
|
];
|
|
|
|
for (file_name, data) in cases {
|
|
let attachment = InlineAttachmentStore
|
|
.upload(UploadRequest {
|
|
thread_id: "thread-1".to_string(),
|
|
ephemeral: false,
|
|
file_name: Some(file_name.to_string()),
|
|
tags: HashMap::new(),
|
|
data: data.to_vec(),
|
|
})
|
|
.await
|
|
.expect("inline attachment");
|
|
let UploadResult::Inline { bytes } = attachment else {
|
|
panic!("inline store returned a file reference");
|
|
};
|
|
|
|
assert_eq!(bytes, data);
|
|
}
|
|
}
|
|
|
|
/// The inline store preserves opaque file references even though it cannot resolve them.
|
|
#[tokio::test]
|
|
async fn inline_store_cannot_resolve_file_references() {
|
|
assert!(InlineAttachmentStore.is_resolvable("file_123").await);
|
|
|
|
let error = InlineAttachmentStore
|
|
.resolve(ResolveRequest {
|
|
file_id: "file_123",
|
|
tags: HashMap::new(),
|
|
download_url_ttl: None,
|
|
})
|
|
.await
|
|
.expect_err("inline store cannot resolve file references");
|
|
|
|
assert_eq!(error.kind(), AttachmentStoreErrorKind::NotFound);
|
|
}
|