Merge remote-tracking branch 'refs/remotes/origin/main' into abhinav/codex-env-file-conflict-fix

# Conflicts:
#	codex-rs/core/src/tools/runtimes/mod.rs
#	codex-rs/core/src/tools/runtimes/shell.rs
#	codex-rs/core/src/tools/runtimes/shell/unix_escalation.rs
#	codex-rs/core/src/tools/runtimes/unified_exec.rs
This commit is contained in:
Abhinav Vedmala
2026-05-28 15:16:25 -07:00
516 changed files with 19606 additions and 5332 deletions
+6
View File
@@ -56,6 +56,9 @@ pub enum TurnItem {
#[derive(Debug, Clone, Deserialize, Serialize, TS, JsonSchema)]
pub struct UserMessageItem {
pub id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub client_id: Option<String>,
pub content: Vec<UserInput>,
}
@@ -237,6 +240,7 @@ impl UserMessageItem {
pub fn new(content: &[UserInput]) -> Self {
Self {
id: uuid::Uuid::new_v4().to_string(),
client_id: None,
content: content.to_vec(),
}
}
@@ -245,6 +249,7 @@ impl UserMessageItem {
// Legacy user-message events flatten only text inputs into `message` and
// rebase text element ranges onto that concatenated text.
EventMsg::UserMessage(UserMessageEvent {
client_id: self.client_id.clone(),
message: self.message(),
images: Some(self.image_urls()),
image_details: self.image_details(),
@@ -272,6 +277,7 @@ impl UserMessageItem {
if let UserInput::Text {
text,
text_elements,
..
} = input
{
// Text element ranges are relative to each text chunk; offset them so they align
+12
View File
@@ -26,6 +26,18 @@ impl std::fmt::Display for RequestId {
}
}
/// Presentation metadata advertised by an initialized MCP server.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct McpServerInfo {
pub name: String,
pub title: Option<String>,
pub version: String,
pub description: Option<String>,
pub icons: Option<Vec<serde_json::Value>>,
pub website_url: Option<String>,
}
/// Definition for a tool the client can call.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
+52 -44
View File
@@ -725,6 +725,8 @@ pub enum ContentItem {
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "lowercase")]
pub enum ImageDetail {
Auto,
Low,
High,
Original,
}
@@ -1077,7 +1079,7 @@ pub fn local_image_content_items_with_label_number(
) -> Vec<ContentItem> {
let mode = match detail {
ImageDetail::Original => PromptImageMode::Original,
ImageDetail::High => PromptImageMode::ResizeToFit,
ImageDetail::Auto | ImageDetail::Low | ImageDetail::High => PromptImageMode::ResizeToFit,
};
match load_for_prompt_bytes(path, file_bytes, mode) {
@@ -1235,23 +1237,17 @@ impl From<Vec<UserInput>> for ResponseInputItem {
.into_iter()
.flat_map(|c| match c {
UserInput::Text { text, .. } => vec![ContentItem::InputText { text }],
UserInput::Image { image_url, detail } => {
UserInput::Image {
image_url, detail, ..
} => {
image_index += 1;
let detail = detail.unwrap_or(DEFAULT_IMAGE_DETAIL);
vec![
ContentItem::InputText {
text: image_open_tag_text(),
},
ContentItem::InputImage {
image_url,
detail: Some(detail),
},
ContentItem::InputText {
text: image_close_tag_text(),
},
]
vec![ContentItem::InputImage {
image_url,
detail: Some(detail),
}]
}
UserInput::LocalImage { path, detail } => {
UserInput::LocalImage { path, detail, .. } => {
image_index += 1;
let detail = detail.unwrap_or(DEFAULT_IMAGE_DETAIL);
match std::fs::read(&path) {
@@ -1607,6 +1603,8 @@ fn convert_mcp_content_to_items(
.and_then(|meta| meta.get(CODEX_IMAGE_DETAIL_META_KEY))
.and_then(serde_json::Value::as_str)
.and_then(|detail| match detail {
"auto" => Some(ImageDetail::Auto),
"low" => Some(ImageDetail::Low),
"high" => Some(ImageDetail::High),
"original" => Some(ImageDetail::Original),
_ => None,
@@ -1682,6 +1680,36 @@ mod tests {
);
}
#[test]
fn image_detail_roundtrips_all_wire_values() -> Result<()> {
assert_eq!(
serde_json::from_str::<ImageDetail>("\"auto\"")?,
ImageDetail::Auto
);
assert_eq!(
serde_json::from_str::<ImageDetail>("\"low\"")?,
ImageDetail::Low
);
assert_eq!(serde_json::to_string(&ImageDetail::Auto)?, "\"auto\"");
assert_eq!(serde_json::to_string(&ImageDetail::Low)?, "\"low\"");
let content_item: ContentItem = serde_json::from_value(serde_json::json!({
"type": "input_image",
"image_url": "data:image/png;base64,abc",
"detail": "auto",
}))?;
assert_eq!(
content_item,
ContentItem::InputImage {
image_url: "data:image/png;base64,abc".to_string(),
detail: Some(ImageDetail::Auto),
}
);
Ok(())
}
#[test]
fn sandbox_permissions_helpers_match_documented_semantics() {
let cases = [
@@ -2622,7 +2650,7 @@ mod tests {
}
#[test]
fn wraps_image_user_input_with_tags() -> Result<()> {
fn serializes_image_user_input_without_tags() -> Result<()> {
let image_url = "data:image/png;base64,abc".to_string();
let item = ResponseInputItem::from(vec![UserInput::Image {
@@ -2632,18 +2660,10 @@ mod tests {
match item {
ResponseInputItem::Message { content, .. } => {
let expected = vec![
ContentItem::InputText {
text: image_open_tag_text(),
},
ContentItem::InputImage {
image_url,
detail: Some(DEFAULT_IMAGE_DETAIL),
},
ContentItem::InputText {
text: image_close_tag_text(),
},
];
let expected = vec![ContentItem::InputImage {
image_url,
detail: Some(DEFAULT_IMAGE_DETAIL),
}];
assert_eq!(content, expected);
}
other => panic!("expected message response but got {other:?}"),
@@ -2664,7 +2684,7 @@ mod tests {
match item {
ResponseInputItem::Message { content, .. } => {
assert_eq!(
content.get(1),
content.first(),
Some(&ContentItem::InputImage {
image_url,
detail: Some(ImageDetail::Original),
@@ -2862,35 +2882,23 @@ mod tests {
ResponseInputItem::Message { content, .. } => {
assert_eq!(
content.first(),
Some(&ContentItem::InputText {
text: image_open_tag_text(),
})
);
assert_eq!(
content.get(1),
Some(&ContentItem::InputImage {
image_url,
detail: Some(DEFAULT_IMAGE_DETAIL),
})
);
assert_eq!(
content.get(2),
Some(&ContentItem::InputText {
text: image_close_tag_text(),
})
);
assert_eq!(
content.get(3),
content.get(1),
Some(&ContentItem::InputText {
text: local_image_open_tag_text(/*label_number*/ 2),
})
);
assert!(matches!(
content.get(4),
content.get(2),
Some(ContentItem::InputImage { .. })
));
assert_eq!(
content.get(5),
content.get(3),
Some(&ContentItem::InputText {
text: image_close_tag_text(),
})
+14 -1
View File
@@ -129,6 +129,9 @@ pub struct Submission {
pub id: String,
/// Payload
pub op: Op,
/// Client-provided id for the user message represented by `Op::UserInput`.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub client_user_message_id: Option<String>,
/// Optional W3C trace carrier propagated across async submission handoffs.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub trace: Option<W3cTraceContext>,
@@ -2164,6 +2167,8 @@ pub struct AgentMessageEvent {
#[derive(Debug, Clone, Default, Deserialize, Serialize, JsonSchema, TS)]
pub struct UserMessageEvent {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub client_id: Option<String>,
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
@@ -2767,6 +2772,10 @@ pub struct TurnContextItem {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub turn_id: Option<String>,
pub cwd: PathBuf,
/// Effective workspace roots used to materialize symbolic
/// `:workspace_roots` filesystem permissions in `permission_profile`.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub workspace_roots: Option<Vec<AbsolutePathBuf>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub current_date: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
@@ -5088,6 +5097,7 @@ mod tests {
#[test]
fn user_message_event_serializes_empty_metadata_vectors() -> Result<()> {
let event = UserMessageEvent {
client_id: None,
message: "hello".to_string(),
images: None,
local_images: Vec::new(),
@@ -5133,7 +5143,7 @@ mod tests {
#[test]
fn user_message_item_legacy_event_preserves_image_details() {
let local_path = PathBuf::from("/tmp/local.png");
let item = UserMessageItem::new(&[
let mut item = UserMessageItem::new(&[
crate::user_input::UserInput::Image {
image_url: "https://example.com/first.png".to_string(),
detail: Some(ImageDetail::Original),
@@ -5147,6 +5157,7 @@ mod tests {
detail: Some(ImageDetail::Original),
},
]);
item.client_id = Some("client-message-1".to_string());
let EventMsg::UserMessage(event) = item.as_legacy_event() else {
panic!("expected user message event");
@@ -5159,6 +5170,7 @@ mod tests {
"https://example.com/second.png".to_string(),
])
);
assert_eq!(event.client_id, Some("client-message-1".to_string()));
assert_eq!(event.image_details, vec![Some(ImageDetail::Original)]);
assert_eq!(event.local_images, vec![local_path]);
assert_eq!(event.local_image_details, vec![Some(ImageDetail::Original)]);
@@ -5204,6 +5216,7 @@ mod tests {
let item = TurnContextItem {
turn_id: None,
cwd: test_path_buf("/tmp"),
workspace_roots: None,
current_date: None,
timezone: None,
approval_policy: AskForApproval::Never,