moving input item from MCP Protocol back to core Protocol (#1740)
- Currently we have duplicate input item. Let's have one source of truth in the core. - Used Requestid type
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
use codex_core::config_types::SandboxMode;
|
||||
use codex_core::protocol::AskForApproval;
|
||||
use codex_core::protocol::EventMsg;
|
||||
use codex_core::protocol::InputItem;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use strum_macros::Display;
|
||||
@@ -21,7 +22,7 @@ pub struct MessageId(pub Uuid);
|
||||
pub struct ToolCallRequest {
|
||||
#[serde(rename = "jsonrpc")]
|
||||
pub jsonrpc: &'static str,
|
||||
pub id: u64,
|
||||
pub id: RequestId,
|
||||
pub method: &'static str,
|
||||
pub params: ToolCallRequestParams,
|
||||
}
|
||||
@@ -38,7 +39,7 @@ pub enum ToolCallRequestParams {
|
||||
impl ToolCallRequestParams {
|
||||
/// Wrap this request in a JSON-RPC request.
|
||||
#[allow(dead_code)]
|
||||
pub fn into_request(self, id: u64) -> ToolCallRequest {
|
||||
pub fn into_request(self, id: RequestId) -> ToolCallRequest {
|
||||
ToolCallRequest {
|
||||
jsonrpc: "2.0",
|
||||
id,
|
||||
@@ -95,70 +96,13 @@ pub struct ConversationStreamArgs {
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct ConversationSendMessageArgs {
|
||||
pub conversation_id: ConversationId,
|
||||
pub content: Vec<MessageInputItem>,
|
||||
pub content: Vec<InputItem>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub parent_message_id: Option<MessageId>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[serde(flatten)]
|
||||
pub conversation_overrides: Option<ConversationOverrides>,
|
||||
}
|
||||
|
||||
/// Input items for a message.
|
||||
/// Following OpenAI's Responses API: https://platform.openai.com/docs/api-reference/responses
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
pub enum MessageInputItem {
|
||||
Text {
|
||||
text: String,
|
||||
},
|
||||
Image {
|
||||
#[serde(flatten)]
|
||||
source: ImageSource,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
detail: Option<ImageDetail>,
|
||||
},
|
||||
File {
|
||||
#[serde(flatten)]
|
||||
source: FileSource,
|
||||
},
|
||||
}
|
||||
|
||||
/// Source of an image.
|
||||
/// Following OpenAI's API: https://platform.openai.com/docs/guides/images-vision#giving-a-model-images-as-input
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum ImageSource {
|
||||
ImageUrl { image_url: String },
|
||||
FileId { file_id: String },
|
||||
}
|
||||
|
||||
/// Source of a file.
|
||||
/// Following OpenAI's Responses API: https://platform.openai.com/docs/guides/pdf-files?api-mode=responses#uploading-files
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum FileSource {
|
||||
Url {
|
||||
file_url: String,
|
||||
},
|
||||
Id {
|
||||
file_id: String,
|
||||
},
|
||||
Base64 {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
filename: Option<String>,
|
||||
// Base64-encoded file contents.
|
||||
file_data: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum ImageDetail {
|
||||
Low,
|
||||
High,
|
||||
Auto,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct ConversationsListArgs {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
@@ -303,6 +247,8 @@ pub enum ClientNotification {
|
||||
#[allow(clippy::expect_used)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
mod tests {
|
||||
use std::path::PathBuf;
|
||||
|
||||
use super::*;
|
||||
use codex_core::protocol::McpInvocation;
|
||||
use codex_core::protocol::McpToolCallBeginEvent;
|
||||
@@ -331,7 +277,7 @@ mod tests {
|
||||
base_instructions: None,
|
||||
});
|
||||
|
||||
let observed = to_val(&req.into_request(2));
|
||||
let observed = to_val(&req.into_request(mcp_types::RequestId::Integer(2)));
|
||||
let expected = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"id": 2,
|
||||
@@ -354,18 +300,12 @@ mod tests {
|
||||
let req = ToolCallRequestParams::ConversationSendMessage(ConversationSendMessageArgs {
|
||||
conversation_id: ConversationId(uuid!("d0f6ecbe-84a2-41c1-b23d-b20473b25eab")),
|
||||
content: vec![
|
||||
MessageInputItem::Text { text: "Hi".into() },
|
||||
MessageInputItem::Image {
|
||||
source: ImageSource::ImageUrl {
|
||||
image_url: "https://example.com/cat.jpg".into(),
|
||||
},
|
||||
detail: Some(ImageDetail::High),
|
||||
InputItem::Text { text: "Hi".into() },
|
||||
InputItem::Image {
|
||||
image_url: "https://example.com/cat.jpg".into(),
|
||||
},
|
||||
MessageInputItem::File {
|
||||
source: FileSource::Base64 {
|
||||
filename: Some("notes.txt".into()),
|
||||
file_data: "Zm9vYmFy".into(),
|
||||
},
|
||||
InputItem::LocalImage {
|
||||
path: "notes.txt".into(),
|
||||
},
|
||||
],
|
||||
parent_message_id: Some(MessageId(uuid!("67e55044-10b1-426f-9247-bb680e5fe0c8"))),
|
||||
@@ -380,7 +320,7 @@ mod tests {
|
||||
}),
|
||||
});
|
||||
|
||||
let observed = to_val(&req.into_request(2));
|
||||
let observed = to_val(&req.into_request(mcp_types::RequestId::Integer(2)));
|
||||
let expected = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"id": 2,
|
||||
@@ -391,8 +331,8 @@ mod tests {
|
||||
"conversation_id": "d0f6ecbe-84a2-41c1-b23d-b20473b25eab",
|
||||
"content": [
|
||||
{ "type": "text", "text": "Hi" },
|
||||
{ "type": "image", "image_url": "https://example.com/cat.jpg", "detail": "high" },
|
||||
{ "type": "file", "filename": "notes.txt", "file_data": "Zm9vYmFy" }
|
||||
{ "type": "image", "image_url": "https://example.com/cat.jpg" },
|
||||
{ "type": "local_image", "path": "notes.txt" }
|
||||
],
|
||||
"parent_message_id": "67e55044-10b1-426f-9247-bb680e5fe0c8",
|
||||
"model": "o4-mini",
|
||||
@@ -414,7 +354,7 @@ mod tests {
|
||||
cursor: Some("abc".into()),
|
||||
});
|
||||
|
||||
let observed = to_val(&req.into_request(2));
|
||||
let observed = to_val(&req.into_request(RequestId::Integer(2)));
|
||||
let expected = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"id": 2,
|
||||
@@ -436,7 +376,7 @@ mod tests {
|
||||
conversation_id: ConversationId(uuid!("67e55044-10b1-426f-9247-bb680e5fe0c8")),
|
||||
});
|
||||
|
||||
let observed = to_val(&req.into_request(2));
|
||||
let observed = to_val(&req.into_request(mcp_types::RequestId::Integer(2)));
|
||||
let expected = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"id": 2,
|
||||
@@ -454,48 +394,44 @@ mod tests {
|
||||
// ----- Message inputs / sources -----
|
||||
|
||||
#[test]
|
||||
fn serialize_message_input_image_file_id_auto_detail() {
|
||||
let item = MessageInputItem::Image {
|
||||
source: ImageSource::FileId {
|
||||
file_id: "file_123".into(),
|
||||
},
|
||||
detail: Some(ImageDetail::Auto),
|
||||
fn serialize_message_input_image_url() {
|
||||
let item = InputItem::Image {
|
||||
image_url: "https://example.com/x.png".into(),
|
||||
};
|
||||
let observed = to_val(&item);
|
||||
let expected = json!({
|
||||
"type": "image",
|
||||
"file_id": "file_123",
|
||||
"detail": "auto"
|
||||
"image_url": "https://example.com/x.png"
|
||||
});
|
||||
assert_eq!(observed, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_message_input_file_url_and_id_variants() {
|
||||
let url = MessageInputItem::File {
|
||||
source: FileSource::Url {
|
||||
file_url: "https://example.com/a.pdf".into(),
|
||||
},
|
||||
fn serialize_message_input_local_image_path() {
|
||||
let url = InputItem::LocalImage {
|
||||
path: PathBuf::from("https://example.com/a.pdf"),
|
||||
};
|
||||
let id = MessageInputItem::File {
|
||||
source: FileSource::Id {
|
||||
file_id: "file_456".into(),
|
||||
},
|
||||
let id = InputItem::LocalImage {
|
||||
path: PathBuf::from("file_456"),
|
||||
};
|
||||
let observed_url = to_val(&url);
|
||||
let expected_url = json!({"type":"local_image","path":"https://example.com/a.pdf"});
|
||||
assert_eq!(
|
||||
to_val(&url),
|
||||
json!({"type":"file","file_url":"https://example.com/a.pdf"})
|
||||
observed_url, expected_url,
|
||||
"LocalImage with URL path should serialize as image_url"
|
||||
);
|
||||
let observed_id = to_val(&id);
|
||||
let expected_id = json!({"type":"local_image","path":"file_456"});
|
||||
assert_eq!(
|
||||
observed_id, expected_id,
|
||||
"LocalImage with file id should serialize as image_url"
|
||||
);
|
||||
assert_eq!(to_val(&id), json!({"type":"file","file_id":"file_456"}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_message_input_image_url_without_detail() {
|
||||
let item = MessageInputItem::Image {
|
||||
source: ImageSource::ImageUrl {
|
||||
image_url: "https://example.com/x.png".into(),
|
||||
},
|
||||
detail: None,
|
||||
let item = InputItem::Image {
|
||||
image_url: "https://example.com/x.png".into(),
|
||||
};
|
||||
let observed = to_val(&item);
|
||||
let expected = json!({
|
||||
|
||||
Reference in New Issue
Block a user