Store generated image items as attachment references (#54398)

## What changed

- Upload generated images to the attachment store and expose `fileId` in image-generation items when available, with inline `result` fallback on upload or decoding failure.
- Preserve file references through legacy events, persisted rollouts, thread history, and task output summaries. Keep model-facing image preparation separate.
- Add attachment request tags and mark generated-image uploads with `usage=image_gen`, preserving the thread's ephemeral setting.
- Update public schemas and SDK types to accept either image representation, retain the Python `ImageGenerationThreadItem` alias, and prefer `fileId` when reading records containing both fields.

## Testing

Add coverage for file-backed generation and rollout persistence, legacy history replay, inline and transitional serialization, task output visibility, and Python SDK alias compatibility.

GitOrigin-RevId: f7a2bb5707e2f277348a547a988bc33c8e96d891
This commit is contained in:
Krish Chainani
2026-10-15 19:51:22 +00:00
committed by copyberry
parent 1ef30bcbe7
commit aabf13307a
64 changed files with 1229 additions and 125 deletions
+7 -1
View File
@@ -1,5 +1,6 @@
//! Storage-neutral attachment persistence interfaces.
use std::collections::HashMap;
use std::error::Error;
use std::fmt;
use std::future::Future;
@@ -42,6 +43,8 @@ pub struct UploadRequest {
pub ephemeral: bool,
/// Optional name associated with the attachment.
pub file_name: Option<String>,
/// Caller-supplied tags used by storage backends for policy decisions.
pub tags: HashMap<String, String>,
/// Attachment bytes to persist.
pub data: Vec<u8>,
}
@@ -53,16 +56,19 @@ impl fmt::Debug for UploadRequest {
.field("thread_id", &self.thread_id)
.field("ephemeral", &self.ephemeral)
.field("file_name", &self.file_name)
.field("tags", &self.tags)
.field("data", &"<redacted>")
.finish()
}
}
/// Parameters supplied to [`AttachmentStore::resolve`].
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ResolveRequest<'a> {
/// Identifier assigned by the attachment store.
pub file_id: &'a str,
/// Caller-supplied tags used by storage backends for policy decisions.
pub tags: HashMap<String, String>,
/// Minimum remaining lifetime required for the returned file URL.
///
/// When absent, implementations must omit the file URL and may return cached metadata. When
+4 -1
View File
@@ -9,6 +9,7 @@ fn attachment_debug_output_redacts_bytes() {
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 {
@@ -18,7 +19,7 @@ fn attachment_debug_output_redacts_bytes() {
assert_eq!(
(format!("{request:?}"), format!("{result:?}")),
(
r#"UploadRequest { thread_id: "thread-1", ephemeral: false, file_name: Some("image.png"), data: "<redacted>" }"#.to_string(),
r#"UploadRequest { thread_id: "thread-1", ephemeral: false, file_name: Some("image.png"), tags: {}, data: "<redacted>" }"#.to_string(),
r#"Inline { bytes: "<redacted>" }"#.to_string(),
)
);
@@ -52,6 +53,7 @@ async fn inline_store_preserves_image_bytes() {
thread_id: "thread-1".to_string(),
ephemeral: false,
file_name: Some(file_name.to_string()),
tags: HashMap::new(),
data: data.to_vec(),
})
.await
@@ -70,6 +72,7 @@ async fn inline_store_cannot_resolve_file_references() {
let error = InlineAttachmentStore
.resolve(ResolveRequest {
file_id: "file_123",
tags: HashMap::new(),
download_url_ttl: None,
})
.await