mirror of
https://github.com/openai/codex.git
synced 2026-09-09 15:58:47 +00:00
## What changed - Add structured `usageLimitExceeded` metadata to failed image-generation items, including the image limit ID and optional reset timestamp. - Preserve the failure metadata in completion events, app-server thread history, thread reads and resumes, and migrated legacy rollouts. - Export the new failure type in the generated JSON and TypeScript schemas. ## Testing - Cover usage-limit responses through item completion, persistence, and thread resume. - Verify legacy rollout migration retains image-generation failure metadata. GitOrigin-RevId: 91c31a2a47456b6b916ee03002ff314b05946dd4
181 lines
5.0 KiB
Rust
181 lines
5.0 KiB
Rust
use pretty_assertions::assert_eq;
|
|
use serde_json::json;
|
|
use ts_rs::TS;
|
|
|
|
use super::ExtensionItem;
|
|
use super::image_generation::ImageGenerationItem;
|
|
use super::sleep::SleepItem;
|
|
use super::web_search::WebSearchAction;
|
|
use super::web_search::WebSearchItem;
|
|
|
|
fn completed_image_generation_item() -> ExtensionItem {
|
|
ExtensionItem::ImageGeneration(ImageGenerationItem {
|
|
id: "image-1".to_string(),
|
|
status: "completed".to_string(),
|
|
revised_prompt: Some("A blue square".to_string()),
|
|
result: "cG5n".to_string(),
|
|
transparent_background: None,
|
|
failure: None,
|
|
saved_path: None,
|
|
})
|
|
}
|
|
|
|
#[test]
|
|
fn image_generation_item_preserves_stable_wire_shape() {
|
|
let item = completed_image_generation_item();
|
|
let value = serde_json::to_value(&item).expect("serialize extension item");
|
|
|
|
assert_eq!(
|
|
value,
|
|
json!({
|
|
"kind": "image_gen.generation",
|
|
"id": "image-1",
|
|
"status": "completed",
|
|
"revisedPrompt": "A blue square",
|
|
"result": "cG5n",
|
|
"transparentBackground": null,
|
|
"failure": null,
|
|
})
|
|
);
|
|
assert_eq!(
|
|
serde_json::from_value::<ExtensionItem>(value).expect("deserialize extension item"),
|
|
item
|
|
);
|
|
assert_eq!(
|
|
serde_json::from_value::<ExtensionItem>(json!({
|
|
"kind": "image_gen.generation",
|
|
"id": "image-1",
|
|
"status": "completed",
|
|
"revisedPrompt": "A blue square",
|
|
"result": "cG5n",
|
|
}))
|
|
.expect("deserialize legacy image-generation item without transparency metadata"),
|
|
item
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn image_generation_item_preserves_authoritative_transparency() {
|
|
let ExtensionItem::ImageGeneration(mut image) = completed_image_generation_item() else {
|
|
panic!("expected image-generation item");
|
|
};
|
|
image.transparent_background = Some(true);
|
|
let item = ExtensionItem::ImageGeneration(image);
|
|
let value = serde_json::to_value(&item).expect("serialize extension item");
|
|
|
|
assert_eq!(
|
|
value,
|
|
json!({
|
|
"kind": "image_gen.generation",
|
|
"id": "image-1",
|
|
"status": "completed",
|
|
"revisedPrompt": "A blue square",
|
|
"result": "cG5n",
|
|
"transparentBackground": true,
|
|
"failure": null,
|
|
})
|
|
);
|
|
assert_eq!(
|
|
serde_json::from_value::<ExtensionItem>(value).expect("deserialize extension item"),
|
|
item
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn image_generation_transparency_is_optional_in_typescript() {
|
|
assert!(
|
|
ImageGenerationItem::inline().contains("transparentBackground?: boolean"),
|
|
"image-generation transparency must remain optional for existing TypeScript clients"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn web_search_item_preserves_stable_wire_shape() {
|
|
let item = ExtensionItem::WebSearch(WebSearchItem {
|
|
id: "search-1".to_string(),
|
|
query: "docs".to_string(),
|
|
action: Some(WebSearchAction::Search {
|
|
query: Some("docs".to_string()),
|
|
queries: None,
|
|
}),
|
|
results: None,
|
|
});
|
|
let value = serde_json::to_value(&item).expect("serialize extension item");
|
|
|
|
assert_eq!(
|
|
value,
|
|
json!({
|
|
"kind": "web.search",
|
|
"id": "search-1",
|
|
"query": "docs",
|
|
"action": {
|
|
"type": "search",
|
|
"query": "docs",
|
|
"queries": null,
|
|
},
|
|
"results": null,
|
|
})
|
|
);
|
|
assert_eq!(
|
|
serde_json::from_value::<ExtensionItem>(value).expect("deserialize extension item"),
|
|
item
|
|
);
|
|
assert_eq!(
|
|
serde_json::from_value::<ExtensionItem>(json!({
|
|
"kind": "web.search",
|
|
"id": "search-1",
|
|
"query": "docs",
|
|
"action": {
|
|
"type": "search",
|
|
"query": "docs",
|
|
"queries": null,
|
|
},
|
|
}))
|
|
.expect("deserialize legacy extension item without results"),
|
|
item
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn sleep_item_preserves_stable_wire_shape() {
|
|
let item = ExtensionItem::Sleep(SleepItem {
|
|
id: "sleep-1".to_string(),
|
|
duration_ms: 1_000,
|
|
});
|
|
let value = serde_json::to_value(&item).expect("serialize extension item");
|
|
|
|
assert_eq!(
|
|
value,
|
|
json!({
|
|
"kind": "clock.sleep",
|
|
"id": "sleep-1",
|
|
"durationMs": 1_000,
|
|
})
|
|
);
|
|
assert_eq!(
|
|
serde_json::from_value::<ExtensionItem>(value).expect("deserialize extension item"),
|
|
item
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn unknown_extension_kind_is_rejected() {
|
|
let value = json!({
|
|
"kind": "image_gen.unknown",
|
|
"id": "image-1",
|
|
});
|
|
|
|
assert!(serde_json::from_value::<ExtensionItem>(value).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn malformed_known_extension_payload_is_rejected() {
|
|
let value = json!({
|
|
"kind": "image_gen.generation",
|
|
"id": "image-1",
|
|
"status": "completed",
|
|
});
|
|
|
|
assert!(serde_json::from_value::<ExtensionItem>(value).is_err());
|
|
}
|