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
@@ -5364,6 +5364,33 @@
"type": "object"
},
{
"anyOf": [
{
"properties": {
"fileId": {
"type": "string"
}
},
"required": [
"fileId"
],
"title": "FileIdThreadItem",
"type": "object"
},
{
"properties": {
"result": {
"type": "string"
}
},
"required": [
"result"
],
"title": "ResultThreadItem",
"type": "object"
}
],
"description": "Either inline generated-image data or its durable attachment-store identity.\n\n`File` must remain first so rollouts written during the migration, which contain both an empty `result` and a `fileId`, retain the file reference when deserialized.",
"properties": {
"failure": {
"anyOf": [
@@ -5379,9 +5406,6 @@
"id": {
"type": "string"
},
"result": {
"type": "string"
},
"revisedPrompt": {
"type": [
"string",
@@ -5418,7 +5442,6 @@
},
"required": [
"id",
"result",
"status",
"type"
],
@@ -22840,6 +22840,33 @@
"type": "object"
},
{
"anyOf": [
{
"properties": {
"fileId": {
"type": "string"
}
},
"required": [
"fileId"
],
"title": "FileIdThreadItem",
"type": "object"
},
{
"properties": {
"result": {
"type": "string"
}
},
"required": [
"result"
],
"title": "ResultThreadItem",
"type": "object"
}
],
"description": "Either inline generated-image data or its durable attachment-store identity.\n\n`File` must remain first so rollouts written during the migration, which contain both an empty `result` and a `fileId`, retain the file reference when deserialized.",
"properties": {
"failure": {
"anyOf": [
@@ -22855,9 +22882,6 @@
"id": {
"type": "string"
},
"result": {
"type": "string"
},
"revisedPrompt": {
"type": [
"string",
@@ -22894,7 +22918,6 @@
},
"required": [
"id",
"result",
"status",
"type"
],
@@ -20554,6 +20554,33 @@
"type": "object"
},
{
"anyOf": [
{
"properties": {
"fileId": {
"type": "string"
}
},
"required": [
"fileId"
],
"title": "FileIdThreadItem",
"type": "object"
},
{
"properties": {
"result": {
"type": "string"
}
},
"required": [
"result"
],
"title": "ResultThreadItem",
"type": "object"
}
],
"description": "Either inline generated-image data or its durable attachment-store identity.\n\n`File` must remain first so rollouts written during the migration, which contain both an empty `result` and a `fileId`, retain the file reference when deserialized.",
"properties": {
"failure": {
"anyOf": [
@@ -20569,9 +20596,6 @@
"id": {
"type": "string"
},
"result": {
"type": "string"
},
"revisedPrompt": {
"type": [
"string",
@@ -20608,7 +20632,6 @@
},
"required": [
"id",
"result",
"status",
"type"
],
@@ -1523,6 +1523,33 @@
"type": "object"
},
{
"anyOf": [
{
"properties": {
"fileId": {
"type": "string"
}
},
"required": [
"fileId"
],
"title": "FileIdThreadItem",
"type": "object"
},
{
"properties": {
"result": {
"type": "string"
}
},
"required": [
"result"
],
"title": "ResultThreadItem",
"type": "object"
}
],
"description": "Either inline generated-image data or its durable attachment-store identity.\n\n`File` must remain first so rollouts written during the migration, which contain both an empty `result` and a `fileId`, retain the file reference when deserialized.",
"properties": {
"failure": {
"anyOf": [
@@ -1538,9 +1565,6 @@
"id": {
"type": "string"
},
"result": {
"type": "string"
},
"revisedPrompt": {
"type": [
"string",
@@ -1577,7 +1601,6 @@
},
"required": [
"id",
"result",
"status",
"type"
],
@@ -1523,6 +1523,33 @@
"type": "object"
},
{
"anyOf": [
{
"properties": {
"fileId": {
"type": "string"
}
},
"required": [
"fileId"
],
"title": "FileIdThreadItem",
"type": "object"
},
{
"properties": {
"result": {
"type": "string"
}
},
"required": [
"result"
],
"title": "ResultThreadItem",
"type": "object"
}
],
"description": "Either inline generated-image data or its durable attachment-store identity.\n\n`File` must remain first so rollouts written during the migration, which contain both an empty `result` and a `fileId`, retain the file reference when deserialized.",
"properties": {
"failure": {
"anyOf": [
@@ -1538,9 +1565,6 @@
"id": {
"type": "string"
},
"result": {
"type": "string"
},
"revisedPrompt": {
"type": [
"string",
@@ -1577,7 +1601,6 @@
},
"required": [
"id",
"result",
"status",
"type"
],
@@ -1726,6 +1726,33 @@
"type": "object"
},
{
"anyOf": [
{
"properties": {
"fileId": {
"type": "string"
}
},
"required": [
"fileId"
],
"title": "FileIdThreadItem",
"type": "object"
},
{
"properties": {
"result": {
"type": "string"
}
},
"required": [
"result"
],
"title": "ResultThreadItem",
"type": "object"
}
],
"description": "Either inline generated-image data or its durable attachment-store identity.\n\n`File` must remain first so rollouts written during the migration, which contain both an empty `result` and a `fileId`, retain the file reference when deserialized.",
"properties": {
"failure": {
"anyOf": [
@@ -1741,9 +1768,6 @@
"id": {
"type": "string"
},
"result": {
"type": "string"
},
"revisedPrompt": {
"type": [
"string",
@@ -1780,7 +1804,6 @@
},
"required": [
"id",
"result",
"status",
"type"
],
@@ -2340,6 +2340,33 @@
"type": "object"
},
{
"anyOf": [
{
"properties": {
"fileId": {
"type": "string"
}
},
"required": [
"fileId"
],
"title": "FileIdThreadItem",
"type": "object"
},
{
"properties": {
"result": {
"type": "string"
}
},
"required": [
"result"
],
"title": "ResultThreadItem",
"type": "object"
}
],
"description": "Either inline generated-image data or its durable attachment-store identity.\n\n`File` must remain first so rollouts written during the migration, which contain both an empty `result` and a `fileId`, retain the file reference when deserialized.",
"properties": {
"failure": {
"anyOf": [
@@ -2355,9 +2382,6 @@
"id": {
"type": "string"
},
"result": {
"type": "string"
},
"revisedPrompt": {
"type": [
"string",
@@ -2394,7 +2418,6 @@
},
"required": [
"id",
"result",
"status",
"type"
],
@@ -1523,6 +1523,33 @@
"type": "object"
},
{
"anyOf": [
{
"properties": {
"fileId": {
"type": "string"
}
},
"required": [
"fileId"
],
"title": "FileIdThreadItem",
"type": "object"
},
{
"properties": {
"result": {
"type": "string"
}
},
"required": [
"result"
],
"title": "ResultThreadItem",
"type": "object"
}
],
"description": "Either inline generated-image data or its durable attachment-store identity.\n\n`File` must remain first so rollouts written during the migration, which contain both an empty `result` and a `fileId`, retain the file reference when deserialized.",
"properties": {
"failure": {
"anyOf": [
@@ -1538,9 +1565,6 @@
"id": {
"type": "string"
},
"result": {
"type": "string"
},
"revisedPrompt": {
"type": [
"string",
@@ -1577,7 +1601,6 @@
},
"required": [
"id",
"result",
"status",
"type"
],
@@ -2131,6 +2131,33 @@
"type": "object"
},
{
"anyOf": [
{
"properties": {
"fileId": {
"type": "string"
}
},
"required": [
"fileId"
],
"title": "FileIdThreadItem",
"type": "object"
},
{
"properties": {
"result": {
"type": "string"
}
},
"required": [
"result"
],
"title": "ResultThreadItem",
"type": "object"
}
],
"description": "Either inline generated-image data or its durable attachment-store identity.\n\n`File` must remain first so rollouts written during the migration, which contain both an empty `result` and a `fileId`, retain the file reference when deserialized.",
"properties": {
"failure": {
"anyOf": [
@@ -2146,9 +2173,6 @@
"id": {
"type": "string"
},
"result": {
"type": "string"
},
"revisedPrompt": {
"type": [
"string",
@@ -2185,7 +2209,6 @@
},
"required": [
"id",
"result",
"status",
"type"
],
@@ -2131,6 +2131,33 @@
"type": "object"
},
{
"anyOf": [
{
"properties": {
"fileId": {
"type": "string"
}
},
"required": [
"fileId"
],
"title": "FileIdThreadItem",
"type": "object"
},
{
"properties": {
"result": {
"type": "string"
}
},
"required": [
"result"
],
"title": "ResultThreadItem",
"type": "object"
}
],
"description": "Either inline generated-image data or its durable attachment-store identity.\n\n`File` must remain first so rollouts written during the migration, which contain both an empty `result` and a `fileId`, retain the file reference when deserialized.",
"properties": {
"failure": {
"anyOf": [
@@ -2146,9 +2173,6 @@
"id": {
"type": "string"
},
"result": {
"type": "string"
},
"revisedPrompt": {
"type": [
"string",
@@ -2185,7 +2209,6 @@
},
"required": [
"id",
"result",
"status",
"type"
],
@@ -2131,6 +2131,33 @@
"type": "object"
},
{
"anyOf": [
{
"properties": {
"fileId": {
"type": "string"
}
},
"required": [
"fileId"
],
"title": "FileIdThreadItem",
"type": "object"
},
{
"properties": {
"result": {
"type": "string"
}
},
"required": [
"result"
],
"title": "ResultThreadItem",
"type": "object"
}
],
"description": "Either inline generated-image data or its durable attachment-store identity.\n\n`File` must remain first so rollouts written during the migration, which contain both an empty `result` and a `fileId`, retain the file reference when deserialized.",
"properties": {
"failure": {
"anyOf": [
@@ -2146,9 +2173,6 @@
"id": {
"type": "string"
},
"result": {
"type": "string"
},
"revisedPrompt": {
"type": [
"string",
@@ -2185,7 +2209,6 @@
},
"required": [
"id",
"result",
"status",
"type"
],
@@ -2392,6 +2392,33 @@
"type": "object"
},
{
"anyOf": [
{
"properties": {
"fileId": {
"type": "string"
}
},
"required": [
"fileId"
],
"title": "FileIdThreadItem",
"type": "object"
},
{
"properties": {
"result": {
"type": "string"
}
},
"required": [
"result"
],
"title": "ResultThreadItem",
"type": "object"
}
],
"description": "Either inline generated-image data or its durable attachment-store identity.\n\n`File` must remain first so rollouts written during the migration, which contain both an empty `result` and a `fileId`, retain the file reference when deserialized.",
"properties": {
"failure": {
"anyOf": [
@@ -2407,9 +2434,6 @@
"id": {
"type": "string"
},
"result": {
"type": "string"
},
"revisedPrompt": {
"type": [
"string",
@@ -2446,7 +2470,6 @@
},
"required": [
"id",
"result",
"status",
"type"
],
@@ -2131,6 +2131,33 @@
"type": "object"
},
{
"anyOf": [
{
"properties": {
"fileId": {
"type": "string"
}
},
"required": [
"fileId"
],
"title": "FileIdThreadItem",
"type": "object"
},
{
"properties": {
"result": {
"type": "string"
}
},
"required": [
"result"
],
"title": "ResultThreadItem",
"type": "object"
}
],
"description": "Either inline generated-image data or its durable attachment-store identity.\n\n`File` must remain first so rollouts written during the migration, which contain both an empty `result` and a `fileId`, retain the file reference when deserialized.",
"properties": {
"failure": {
"anyOf": [
@@ -2146,9 +2173,6 @@
"id": {
"type": "string"
},
"result": {
"type": "string"
},
"revisedPrompt": {
"type": [
"string",
@@ -2185,7 +2209,6 @@
},
"required": [
"id",
"result",
"status",
"type"
],
@@ -2340,6 +2340,33 @@
"type": "object"
},
{
"anyOf": [
{
"properties": {
"fileId": {
"type": "string"
}
},
"required": [
"fileId"
],
"title": "FileIdThreadItem",
"type": "object"
},
{
"properties": {
"result": {
"type": "string"
}
},
"required": [
"result"
],
"title": "ResultThreadItem",
"type": "object"
}
],
"description": "Either inline generated-image data or its durable attachment-store identity.\n\n`File` must remain first so rollouts written during the migration, which contain both an empty `result` and a `fileId`, retain the file reference when deserialized.",
"properties": {
"failure": {
"anyOf": [
@@ -2355,9 +2382,6 @@
"id": {
"type": "string"
},
"result": {
"type": "string"
},
"revisedPrompt": {
"type": [
"string",
@@ -2394,7 +2418,6 @@
},
"required": [
"id",
"result",
"status",
"type"
],
@@ -2131,6 +2131,33 @@
"type": "object"
},
{
"anyOf": [
{
"properties": {
"fileId": {
"type": "string"
}
},
"required": [
"fileId"
],
"title": "FileIdThreadItem",
"type": "object"
},
{
"properties": {
"result": {
"type": "string"
}
},
"required": [
"result"
],
"title": "ResultThreadItem",
"type": "object"
}
],
"description": "Either inline generated-image data or its durable attachment-store identity.\n\n`File` must remain first so rollouts written during the migration, which contain both an empty `result` and a `fileId`, retain the file reference when deserialized.",
"properties": {
"failure": {
"anyOf": [
@@ -2146,9 +2173,6 @@
"id": {
"type": "string"
},
"result": {
"type": "string"
},
"revisedPrompt": {
"type": [
"string",
@@ -2185,7 +2209,6 @@
},
"required": [
"id",
"result",
"status",
"type"
],
@@ -1726,6 +1726,33 @@
"type": "object"
},
{
"anyOf": [
{
"properties": {
"fileId": {
"type": "string"
}
},
"required": [
"fileId"
],
"title": "FileIdThreadItem",
"type": "object"
},
{
"properties": {
"result": {
"type": "string"
}
},
"required": [
"result"
],
"title": "ResultThreadItem",
"type": "object"
}
],
"description": "Either inline generated-image data or its durable attachment-store identity.\n\n`File` must remain first so rollouts written during the migration, which contain both an empty `result` and a `fileId`, retain the file reference when deserialized.",
"properties": {
"failure": {
"anyOf": [
@@ -1741,9 +1768,6 @@
"id": {
"type": "string"
},
"result": {
"type": "string"
},
"revisedPrompt": {
"type": [
"string",
@@ -1780,7 +1804,6 @@
},
"required": [
"id",
"result",
"status",
"type"
],
@@ -2131,6 +2131,33 @@
"type": "object"
},
{
"anyOf": [
{
"properties": {
"fileId": {
"type": "string"
}
},
"required": [
"fileId"
],
"title": "FileIdThreadItem",
"type": "object"
},
{
"properties": {
"result": {
"type": "string"
}
},
"required": [
"result"
],
"title": "ResultThreadItem",
"type": "object"
}
],
"description": "Either inline generated-image data or its durable attachment-store identity.\n\n`File` must remain first so rollouts written during the migration, which contain both an empty `result` and a `fileId`, retain the file reference when deserialized.",
"properties": {
"failure": {
"anyOf": [
@@ -2146,9 +2173,6 @@
"id": {
"type": "string"
},
"result": {
"type": "string"
},
"revisedPrompt": {
"type": [
"string",
@@ -2185,7 +2209,6 @@
},
"required": [
"id",
"result",
"status",
"type"
],
@@ -1726,6 +1726,33 @@
"type": "object"
},
{
"anyOf": [
{
"properties": {
"fileId": {
"type": "string"
}
},
"required": [
"fileId"
],
"title": "FileIdThreadItem",
"type": "object"
},
{
"properties": {
"result": {
"type": "string"
}
},
"required": [
"result"
],
"title": "ResultThreadItem",
"type": "object"
}
],
"description": "Either inline generated-image data or its durable attachment-store identity.\n\n`File` must remain first so rollouts written during the migration, which contain both an empty `result` and a `fileId`, retain the file reference when deserialized.",
"properties": {
"failure": {
"anyOf": [
@@ -1741,9 +1768,6 @@
"id": {
"type": "string"
},
"result": {
"type": "string"
},
"revisedPrompt": {
"type": [
"string",
@@ -1780,7 +1804,6 @@
},
"required": [
"id",
"result",
"status",
"type"
],
@@ -1726,6 +1726,33 @@
"type": "object"
},
{
"anyOf": [
{
"properties": {
"fileId": {
"type": "string"
}
},
"required": [
"fileId"
],
"title": "FileIdThreadItem",
"type": "object"
},
{
"properties": {
"result": {
"type": "string"
}
},
"required": [
"result"
],
"title": "ResultThreadItem",
"type": "object"
}
],
"description": "Either inline generated-image data or its durable attachment-store identity.\n\n`File` must remain first so rollouts written during the migration, which contain both an empty `result` and a `fileId`, retain the file reference when deserialized.",
"properties": {
"failure": {
"anyOf": [
@@ -1741,9 +1768,6 @@
"id": {
"type": "string"
},
"result": {
"type": "string"
},
"revisedPrompt": {
"type": [
"string",
@@ -1780,7 +1804,6 @@
},
"required": [
"id",
"result",
"status",
"type"
],
@@ -1726,6 +1726,33 @@
"type": "object"
},
{
"anyOf": [
{
"properties": {
"fileId": {
"type": "string"
}
},
"required": [
"fileId"
],
"title": "FileIdThreadItem",
"type": "object"
},
{
"properties": {
"result": {
"type": "string"
}
},
"required": [
"result"
],
"title": "ResultThreadItem",
"type": "object"
}
],
"description": "Either inline generated-image data or its durable attachment-store identity.\n\n`File` must remain first so rollouts written during the migration, which contain both an empty `result` and a `fileId`, retain the file reference when deserialized.",
"properties": {
"failure": {
"anyOf": [
@@ -1741,9 +1768,6 @@
"id": {
"type": "string"
},
"result": {
"type": "string"
},
"revisedPrompt": {
"type": [
"string",
@@ -1780,7 +1804,6 @@
},
"required": [
"id",
"result",
"status",
"type"
],
@@ -4,4 +4,4 @@
import type { AbsolutePathBuf } from "./AbsolutePathBuf";
import type { ImageGenerationFailure } from "./ImageGenerationFailure";
export type ImageGenerationItem = { id: string, status: string, revisedPrompt: string | null, result: string, transparentBackground?: boolean, failure: ImageGenerationFailure | null, savedPath?: AbsolutePathBuf, };
export type ImageGenerationItem = { id: string, status: string, revisedPrompt: string | null, transparentBackground?: boolean, failure: ImageGenerationFailure | null, savedPath?: AbsolutePathBuf, } & ({ fileId: string, } | { result: string, });