Add text element metadata to types (#9235)

Initial type tweaking PR to make the diff of
https://github.com/openai/codex/pull/9116 smaller

This should not change any behavior, just adds some fields to types
This commit is contained in:
charley-oai
2026-01-14 16:41:50 -08:00
committed by GitHub
parent 2a68b74b9b
commit 4a9c2bcc5a
62 changed files with 483 additions and 41 deletions

View File

@@ -65,6 +65,10 @@ impl UserMessageItem {
EventMsg::UserMessage(UserMessageEvent {
message: self.message(),
images: Some(self.image_urls()),
// TODO: Thread text element ranges into legacy user message events.
text_elements: Vec::new(),
// TODO: Thread local image paths into legacy user message events.
local_images: Vec::new(),
})
}
@@ -72,7 +76,7 @@ impl UserMessageItem {
self.content
.iter()
.map(|c| match c {
UserInput::Text { text } => text.clone(),
UserInput::Text { text, .. } => text.clone(),
_ => String::new(),
})
.collect::<Vec<String>>()

View File

@@ -550,7 +550,7 @@ impl From<Vec<UserInput>> for ResponseInputItem {
content: items
.into_iter()
.flat_map(|c| match c {
UserInput::Text { text } => vec![ContentItem::InputText { text }],
UserInput::Text { text, .. } => vec![ContentItem::InputText { text }],
UserInput::Image { image_url } => vec![
ContentItem::InputText {
text: image_open_tag_text(),

View File

@@ -1255,8 +1255,19 @@ pub struct AgentMessageEvent {
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
pub struct UserMessageEvent {
pub message: String,
/// Image URLs sourced from `UserInput::Image`. These are safe
/// to replay in legacy UI history events and correspond to images sent to
/// the model.
#[serde(skip_serializing_if = "Option::is_none")]
pub images: Option<Vec<String>>,
/// Local file paths sourced from `UserInput::LocalImage`. These are kept so
/// the UI can reattach images when editing history, and should not be sent
/// to the model or treated as API-ready URLs.
#[serde(default)]
pub local_images: Vec<std::path::PathBuf>,
/// UI-defined spans within `message` used to render or persist special elements.
#[serde(default)]
pub text_elements: Vec<crate::user_input::TextElement>,
}
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
@@ -2298,6 +2309,48 @@ mod tests {
Ok(())
}
#[test]
fn user_input_text_serializes_empty_text_elements() -> Result<()> {
let input = UserInput::Text {
text: "hello".to_string(),
text_elements: Vec::new(),
};
let json_input = serde_json::to_value(input)?;
assert_eq!(
json_input,
json!({
"type": "text",
"text": "hello",
"text_elements": [],
})
);
Ok(())
}
#[test]
fn user_message_event_serializes_empty_metadata_vectors() -> Result<()> {
let event = UserMessageEvent {
message: "hello".to_string(),
images: None,
local_images: Vec::new(),
text_elements: Vec::new(),
};
let json_event = serde_json::to_value(event)?;
assert_eq!(
json_event,
json!({
"message": "hello",
"local_images": [],
"text_elements": [],
})
);
Ok(())
}
/// Serialize Event to verify that its JSON representation has the expected
/// amount of nesting.
#[test]

View File

@@ -10,17 +10,19 @@ use ts_rs::TS;
pub enum UserInput {
Text {
text: String,
/// UI-defined spans within `text` that should be treated as special elements.
/// These are byte ranges into the UTF-8 `text` buffer and are used to render
/// or persist rich input markers (e.g., image placeholders) across history
/// and resume without mutating the literal text.
#[serde(default)]
text_elements: Vec<TextElement>,
},
/// Preencoded data: URI image.
Image {
image_url: String,
},
Image { image_url: String },
/// Local image path provided by the user. This will be converted to an
/// `Image` variant (base64 data URL) during request serialization.
LocalImage {
path: std::path::PathBuf,
},
LocalImage { path: std::path::PathBuf },
/// Skill selected by the user (name + path to SKILL.md).
Skill {
@@ -28,3 +30,28 @@ pub enum UserInput {
path: std::path::PathBuf,
},
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, TS, JsonSchema)]
pub struct TextElement {
/// Byte range in the parent `text` buffer that this element occupies.
pub byte_range: ByteRange,
/// Optional human-readable placeholder for the element, displayed in the UI.
pub placeholder: Option<String>,
}
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, TS, JsonSchema)]
pub struct ByteRange {
/// Start byte offset (inclusive) within the UTF-8 text buffer.
pub start: usize,
/// End byte offset (exclusive) within the UTF-8 text buffer.
pub end: usize,
}
impl From<std::ops::Range<usize>> for ByteRange {
fn from(range: std::ops::Range<usize>) -> Self {
Self {
start: range.start,
end: range.end,
}
}
}