mirror of
https://github.com/openai/codex.git
synced 2026-10-21 17:48:44 +00:00
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:
@@ -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
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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(),
|
||||
})
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user