mirror of
https://github.com/openai/codex.git
synced 2026-10-28 18:58:46 +00:00
Expose and persist image generation IDs (#57552)
## What changed Expose the backend-provided `generationId` on image-generation items and preserve it through legacy completion events, thread history reconstruction, and rollout migration. Default missing IDs to `None` for older history. Update the generated JSON schemas, TypeScript types, and Python models to include the nullable field. ## Testing Extend coverage for ID serialization round trips, completed-item notifications, persisted and resumed history, and legacy rollout migration. GitOrigin-RevId: a7f06faa7ac6833417b257a535d2901c03b79135
This commit is contained in:
@@ -5427,6 +5427,14 @@
|
||||
],
|
||||
"default": null
|
||||
},
|
||||
"generationId": {
|
||||
"default": null,
|
||||
"description": "Image generation identifier for the generated image, when provided by the image backend.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
+8
@@ -23211,6 +23211,14 @@
|
||||
],
|
||||
"default": null
|
||||
},
|
||||
"generationId": {
|
||||
"default": null,
|
||||
"description": "Image generation identifier for the generated image, when provided by the image backend.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
+8
@@ -20925,6 +20925,14 @@
|
||||
],
|
||||
"default": null
|
||||
},
|
||||
"generationId": {
|
||||
"default": null,
|
||||
"description": "Image generation identifier for the generated image, when provided by the image backend.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -1580,6 +1580,14 @@
|
||||
],
|
||||
"default": null
|
||||
},
|
||||
"generationId": {
|
||||
"default": null,
|
||||
"description": "Image generation identifier for the generated image, when provided by the image backend.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -1580,6 +1580,14 @@
|
||||
],
|
||||
"default": null
|
||||
},
|
||||
"generationId": {
|
||||
"default": null,
|
||||
"description": "Image generation identifier for the generated image, when provided by the image backend.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -1785,6 +1785,14 @@
|
||||
],
|
||||
"default": null
|
||||
},
|
||||
"generationId": {
|
||||
"default": null,
|
||||
"description": "Image generation identifier for the generated image, when provided by the image backend.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -2399,6 +2399,14 @@
|
||||
],
|
||||
"default": null
|
||||
},
|
||||
"generationId": {
|
||||
"default": null,
|
||||
"description": "Image generation identifier for the generated image, when provided by the image backend.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -1580,6 +1580,14 @@
|
||||
],
|
||||
"default": null
|
||||
},
|
||||
"generationId": {
|
||||
"default": null,
|
||||
"description": "Image generation identifier for the generated image, when provided by the image backend.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -2190,6 +2190,14 @@
|
||||
],
|
||||
"default": null
|
||||
},
|
||||
"generationId": {
|
||||
"default": null,
|
||||
"description": "Image generation identifier for the generated image, when provided by the image backend.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
+8
@@ -2190,6 +2190,14 @@
|
||||
],
|
||||
"default": null
|
||||
},
|
||||
"generationId": {
|
||||
"default": null,
|
||||
"description": "Image generation identifier for the generated image, when provided by the image backend.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -2190,6 +2190,14 @@
|
||||
],
|
||||
"default": null
|
||||
},
|
||||
"generationId": {
|
||||
"default": null,
|
||||
"description": "Image generation identifier for the generated image, when provided by the image backend.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -2451,6 +2451,14 @@
|
||||
],
|
||||
"default": null
|
||||
},
|
||||
"generationId": {
|
||||
"default": null,
|
||||
"description": "Image generation identifier for the generated image, when provided by the image backend.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -2190,6 +2190,14 @@
|
||||
],
|
||||
"default": null
|
||||
},
|
||||
"generationId": {
|
||||
"default": null,
|
||||
"description": "Image generation identifier for the generated image, when provided by the image backend.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -2399,6 +2399,14 @@
|
||||
],
|
||||
"default": null
|
||||
},
|
||||
"generationId": {
|
||||
"default": null,
|
||||
"description": "Image generation identifier for the generated image, when provided by the image backend.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -2190,6 +2190,14 @@
|
||||
],
|
||||
"default": null
|
||||
},
|
||||
"generationId": {
|
||||
"default": null,
|
||||
"description": "Image generation identifier for the generated image, when provided by the image backend.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -1785,6 +1785,14 @@
|
||||
],
|
||||
"default": null
|
||||
},
|
||||
"generationId": {
|
||||
"default": null,
|
||||
"description": "Image generation identifier for the generated image, when provided by the image backend.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -2190,6 +2190,14 @@
|
||||
],
|
||||
"default": null
|
||||
},
|
||||
"generationId": {
|
||||
"default": null,
|
||||
"description": "Image generation identifier for the generated image, when provided by the image backend.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -1785,6 +1785,14 @@
|
||||
],
|
||||
"default": null
|
||||
},
|
||||
"generationId": {
|
||||
"default": null,
|
||||
"description": "Image generation identifier for the generated image, when provided by the image backend.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -1785,6 +1785,14 @@
|
||||
],
|
||||
"default": null
|
||||
},
|
||||
"generationId": {
|
||||
"default": null,
|
||||
"description": "Image generation identifier for the generated image, when provided by the image backend.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -1785,6 +1785,14 @@
|
||||
],
|
||||
"default": null
|
||||
},
|
||||
"generationId": {
|
||||
"default": null,
|
||||
"description": "Image generation identifier for the generated image, when provided by the image backend.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -9,4 +9,8 @@ export type ImageGenerationItem = { id: string, status: string, revisedPrompt: s
|
||||
/**
|
||||
* Observed local inputs for a completed generation, absent on older items.
|
||||
*/
|
||||
inputImages?: Array<ImageGenerationInputImage>, } & ({ fileId: string, } | { result: string, });
|
||||
inputImages?: Array<ImageGenerationInputImage>,
|
||||
/**
|
||||
* Image generation identifier for the generated image, when provided by the image backend.
|
||||
*/
|
||||
generationId: string | null, } & ({ fileId: string, } | { result: string, });
|
||||
|
||||
@@ -1043,7 +1043,7 @@ impl ThreadHistoryBuilder {
|
||||
saved_path: payload.saved_path.clone(),
|
||||
input_images: payload.input_images.clone(),
|
||||
imagegen_request_id: None,
|
||||
generation_id: None,
|
||||
generation_id: payload.generation_id.clone(),
|
||||
});
|
||||
self.upsert_item_in_current_turn(item);
|
||||
}
|
||||
@@ -2929,6 +2929,7 @@ mod tests {
|
||||
path: test_path_buf("/tmp/reference.png").abs(),
|
||||
sha256: "ab".repeat(32),
|
||||
}],
|
||||
generation_id: None,
|
||||
})),
|
||||
RolloutItem::EventMsg(EventMsg::TurnComplete(TurnCompleteEvent {
|
||||
root_turn_id: None,
|
||||
|
||||
@@ -168,6 +168,7 @@ async fn standalone_image_generation_returns_saved_path_hint_to_model(
|
||||
image,
|
||||
transparent_background,
|
||||
saved_path: Some(saved_path),
|
||||
generation_id,
|
||||
..
|
||||
}) = completed.item.payload
|
||||
else {
|
||||
@@ -175,6 +176,7 @@ async fn standalone_image_generation_returns_saved_path_hint_to_model(
|
||||
};
|
||||
assert_eq!(status, "completed");
|
||||
assert_eq!(revised_prompt.as_deref(), Some("paint a blue whale"));
|
||||
assert_eq!(generation_id.as_deref(), Some("gen-image-123"));
|
||||
assert_eq!(
|
||||
image,
|
||||
GeneratedImageRef::Inline {
|
||||
@@ -398,6 +400,10 @@ async fn transparent_image_preserves_output_metadata_and_persisted_history(
|
||||
})
|
||||
.context("persisted legacy history should contain the generated image")?;
|
||||
assert_eq!(persisted_image.transparent_background, Some(true));
|
||||
assert_eq!(
|
||||
persisted_image.generation_id.as_deref(),
|
||||
Some("gen-transparent")
|
||||
);
|
||||
assert_eq!(persisted_image.input_images, expected_inputs);
|
||||
|
||||
let resume_id = resumed
|
||||
@@ -418,6 +424,10 @@ async fn transparent_image_preserves_output_metadata_and_persisted_history(
|
||||
})
|
||||
.context("resumed legacy history should contain the generated image")?;
|
||||
assert_eq!(resumed_image.transparent_background, Some(true));
|
||||
assert_eq!(
|
||||
resumed_image.generation_id.as_deref(),
|
||||
Some("gen-transparent")
|
||||
);
|
||||
assert_eq!(resumed_image.input_images, expected_inputs);
|
||||
|
||||
Ok(())
|
||||
@@ -1255,7 +1265,7 @@ async fn mount_image_response_with_background(server: &MockServer, background: &
|
||||
.set_body_json(json!({
|
||||
"created": 1,
|
||||
"background": background,
|
||||
"data": [{"b64_json": RESULT}],
|
||||
"data": [{"b64_json": RESULT, "generation_id": "gen-transparent"}],
|
||||
})),
|
||||
)
|
||||
.expect(1)
|
||||
@@ -1269,7 +1279,7 @@ async fn mount_image_edit_response(server: &MockServer) {
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
|
||||
"created": 1,
|
||||
"background": "transparent",
|
||||
"data": [{"b64_json": RESULT}],
|
||||
"data": [{"b64_json": RESULT, "generation_id": "gen-transparent"}],
|
||||
})))
|
||||
.expect(1)
|
||||
.mount(server)
|
||||
|
||||
@@ -3099,6 +3099,7 @@ fn append_resume_redaction_history(
|
||||
failure: None,
|
||||
saved_path: Some(test_absolute_path("/tmp/ig-1.png")),
|
||||
input_images: Vec::new(),
|
||||
generation_id: None,
|
||||
}),
|
||||
]
|
||||
.into_iter()
|
||||
|
||||
@@ -660,6 +660,7 @@ mod tests {
|
||||
failure: None,
|
||||
saved_path: Some(expected_path.clone()),
|
||||
input_images: Vec::new(),
|
||||
generation_id: None,
|
||||
})],
|
||||
},
|
||||
)
|
||||
|
||||
@@ -114,6 +114,7 @@ fn legacy_end_event(item: &ImageGenerationItem) -> EventMsg {
|
||||
failure: item.failure.clone(),
|
||||
saved_path: item.saved_path.clone(),
|
||||
input_images: item.input_images.clone(),
|
||||
generation_id: item.generation_id.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -61,10 +61,8 @@ pub struct ImageGenerationItem {
|
||||
#[schemars(skip)]
|
||||
#[ts(skip)]
|
||||
pub imagegen_request_id: Option<String>,
|
||||
/// Backend generation ID for this exact image, retained only for in-process analytics.
|
||||
#[serde(skip)]
|
||||
#[schemars(skip)]
|
||||
#[ts(skip)]
|
||||
/// Image generation identifier for the generated image, when provided by the image backend.
|
||||
#[serde(default)]
|
||||
pub generation_id: Option<String>,
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ fn image_generation_item_preserves_stable_wire_shape() {
|
||||
"result": "cG5n",
|
||||
"transparentBackground": null,
|
||||
"failure": null,
|
||||
"generationId": null,
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -126,6 +127,7 @@ fn image_generation_item_reads_transitional_file_wire_shape() {
|
||||
"fileId": "file-1",
|
||||
"transparentBackground": null,
|
||||
"failure": null,
|
||||
"generationId": null,
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -173,6 +175,7 @@ fn image_generation_item_preserves_authoritative_transparency() {
|
||||
"result": "cG5n",
|
||||
"transparentBackground": true,
|
||||
"failure": null,
|
||||
"generationId": null,
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -205,16 +208,19 @@ fn image_generation_request_id_stays_internal() {
|
||||
let value = serde_json::to_value(&item).expect("serialize extension item");
|
||||
|
||||
assert!(value.get("imagegenRequestId").is_none());
|
||||
assert!(value.get("generationId").is_none());
|
||||
assert_eq!(value["generationId"], json!("gen-image-123"));
|
||||
let ExtensionItem::ImageGeneration(round_tripped) =
|
||||
serde_json::from_value::<ExtensionItem>(value).expect("deserialize extension item")
|
||||
else {
|
||||
panic!("expected image-generation item");
|
||||
};
|
||||
assert_eq!(round_tripped.imagegen_request_id, None);
|
||||
assert_eq!(round_tripped.generation_id, None);
|
||||
assert_eq!(
|
||||
round_tripped.generation_id.as_deref(),
|
||||
Some("gen-image-123")
|
||||
);
|
||||
assert!(!ImageGenerationItem::inline().contains("imagegenRequestId"));
|
||||
assert!(!ImageGenerationItem::inline().contains("generationId"));
|
||||
assert!(ImageGenerationItem::inline().contains("generationId: string | null"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -471,6 +471,7 @@ impl ImageGenerationItem {
|
||||
failure: None,
|
||||
saved_path: self.saved_path.clone(),
|
||||
input_images: Vec::new(),
|
||||
generation_id: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3035,6 +3035,9 @@ pub struct ImageGenerationEndEvent {
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
#[ts(as = "Option<_>", optional)]
|
||||
pub input_images: Vec<ImageGenerationInputImage>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub generation_id: Option<String>,
|
||||
}
|
||||
|
||||
// Conversation kept for backward compatibility.
|
||||
|
||||
@@ -69,6 +69,7 @@ fn legacy_image_generation_preserves_file_reference() {
|
||||
failure: None,
|
||||
saved_path: None,
|
||||
input_images: Vec::new(),
|
||||
generation_id: None,
|
||||
});
|
||||
|
||||
let persisted = persisted_event_msg(&event, ThreadHistoryMode::Legacy)
|
||||
|
||||
@@ -223,7 +223,7 @@ pub(super) fn completed_item(
|
||||
saved_path: event.saved_path.clone(),
|
||||
input_images: event.input_images.clone(),
|
||||
imagegen_request_id: None,
|
||||
generation_id: None,
|
||||
generation_id: event.generation_id.clone(),
|
||||
})),
|
||||
None,
|
||||
)),
|
||||
|
||||
@@ -558,6 +558,7 @@ async fn migration_preserves_image_generation_failure_metadata() {
|
||||
failure: expected_item.failure.clone(),
|
||||
saved_path: expected_item.saved_path.clone(),
|
||||
input_images: Vec::new(),
|
||||
generation_id: expected_item.generation_id.clone(),
|
||||
}));
|
||||
let path = write_rollout(
|
||||
home.path(),
|
||||
@@ -609,7 +610,7 @@ async fn migration_preserves_image_generation_input_observations() {
|
||||
sha256: "ab".repeat(32),
|
||||
}],
|
||||
imagegen_request_id: None,
|
||||
generation_id: None,
|
||||
generation_id: Some("gen-image-123".to_string()),
|
||||
};
|
||||
let image_completion =
|
||||
RolloutItem::EventMsg(EventMsg::ImageGenerationEnd(ImageGenerationEndEvent {
|
||||
@@ -621,6 +622,7 @@ async fn migration_preserves_image_generation_input_observations() {
|
||||
failure: expected_item.failure.clone(),
|
||||
saved_path: expected_item.saved_path.clone(),
|
||||
input_images: expected_item.input_images.clone(),
|
||||
generation_id: expected_item.generation_id.clone(),
|
||||
}));
|
||||
let path = write_rollout(
|
||||
home.path(),
|
||||
|
||||
@@ -5558,6 +5558,13 @@ class FileIdThreadItem(BaseModel):
|
||||
),
|
||||
] = None
|
||||
failure: ImageGenerationFailure | None = None
|
||||
generation_id: Annotated[
|
||||
str | None,
|
||||
Field(
|
||||
alias="generationId",
|
||||
description="Image generation identifier for the generated image, when provided by the image backend.",
|
||||
),
|
||||
] = None
|
||||
id: str
|
||||
input_images: Annotated[
|
||||
list[ImageGenerationInputImage] | None,
|
||||
@@ -5593,6 +5600,13 @@ class ResultThreadItem(BaseModel):
|
||||
),
|
||||
] = None
|
||||
failure: ImageGenerationFailure | None = None
|
||||
generation_id: Annotated[
|
||||
str | None,
|
||||
Field(
|
||||
alias="generationId",
|
||||
description="Image generation identifier for the generated image, when provided by the image backend.",
|
||||
),
|
||||
] = None
|
||||
id: str
|
||||
input_images: Annotated[
|
||||
list[ImageGenerationInputImage] | None,
|
||||
|
||||
Reference in New Issue
Block a user