181 lines
5.5 KiB
Rust
181 lines
5.5 KiB
Rust
|
|
use std::collections::HashMap;
|
|||
|
|
use std::fmt::Display;
|
|||
|
|
|
|||
|
|
use mcp_types::RequestId;
|
|||
|
|
use serde::Deserialize;
|
|||
|
|
use serde::Serialize;
|
|||
|
|
|
|||
|
|
use crate::codex_tool_config::CodexToolCallApprovalPolicy;
|
|||
|
|
use crate::codex_tool_config::CodexToolCallSandboxMode;
|
|||
|
|
use uuid::Uuid;
|
|||
|
|
|
|||
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
|||
|
|
#[serde(transparent)]
|
|||
|
|
pub struct ConversationId(pub Uuid);
|
|||
|
|
|
|||
|
|
impl Display for ConversationId {
|
|||
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|||
|
|
write!(f, "{}", self.0)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// Request from the client to the server.
|
|||
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
|||
|
|
#[serde(tag = "method", rename_all = "camelCase")]
|
|||
|
|
pub enum CodexRequest {
|
|||
|
|
NewConversation {
|
|||
|
|
#[serde(rename = "id")]
|
|||
|
|
request_id: RequestId,
|
|||
|
|
params: NewConversationParams,
|
|||
|
|
},
|
|||
|
|
SendUserMessage {
|
|||
|
|
#[serde(rename = "id")]
|
|||
|
|
request_id: RequestId,
|
|||
|
|
params: SendUserMessageParams,
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
AddConversationListener {
|
|||
|
|
#[serde(rename = "id")]
|
|||
|
|
request_id: RequestId,
|
|||
|
|
params: AddConversationListenerParams,
|
|||
|
|
},
|
|||
|
|
RemoveConversationListener {
|
|||
|
|
#[serde(rename = "id")]
|
|||
|
|
request_id: RequestId,
|
|||
|
|
params: RemoveConversationListenerParams,
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)]
|
|||
|
|
#[serde(rename_all = "camelCase")]
|
|||
|
|
pub struct NewConversationParams {
|
|||
|
|
/// Optional override for the model name (e.g. "o3", "o4-mini").
|
|||
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|||
|
|
pub model: Option<String>,
|
|||
|
|
|
|||
|
|
/// Configuration profile from config.toml to specify default options.
|
|||
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|||
|
|
pub profile: Option<String>,
|
|||
|
|
|
|||
|
|
/// Working directory for the session. If relative, it is resolved against
|
|||
|
|
/// the server process's current working directory.
|
|||
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|||
|
|
pub cwd: Option<String>,
|
|||
|
|
|
|||
|
|
/// Approval policy for shell commands generated by the model:
|
|||
|
|
/// `untrusted`, `on-failure`, `on-request`, `never`.
|
|||
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|||
|
|
pub approval_policy: Option<CodexToolCallApprovalPolicy>,
|
|||
|
|
|
|||
|
|
/// Sandbox mode: `read-only`, `workspace-write`, or `danger-full-access`.
|
|||
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|||
|
|
pub sandbox: Option<CodexToolCallSandboxMode>,
|
|||
|
|
|
|||
|
|
/// Individual config settings that will override what is in
|
|||
|
|
/// CODEX_HOME/config.toml.
|
|||
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|||
|
|
pub config: Option<HashMap<String, serde_json::Value>>,
|
|||
|
|
|
|||
|
|
/// The set of instructions to use instead of the default ones.
|
|||
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|||
|
|
pub base_instructions: Option<String>,
|
|||
|
|
|
|||
|
|
/// Whether to include the plan tool in the conversation.
|
|||
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|||
|
|
pub include_plan_tool: Option<bool>,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
|||
|
|
#[serde(rename_all = "camelCase")]
|
|||
|
|
pub struct NewConversationResponse {
|
|||
|
|
pub conversation_id: ConversationId,
|
|||
|
|
pub model: String,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
|||
|
|
#[serde(rename_all = "camelCase")]
|
|||
|
|
pub struct AddConversationSubscriptionResponse {
|
|||
|
|
pub subscription_id: Uuid,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
|||
|
|
#[serde(rename_all = "camelCase")]
|
|||
|
|
pub struct RemoveConversationSubscriptionResponse {}
|
|||
|
|
|
|||
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
|||
|
|
#[serde(rename_all = "camelCase")]
|
|||
|
|
pub struct SendUserMessageParams {
|
|||
|
|
pub conversation_id: ConversationId,
|
|||
|
|
pub items: Vec<InputItem>,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
|||
|
|
#[serde(rename_all = "camelCase")]
|
|||
|
|
pub struct SendUserMessageResponse {}
|
|||
|
|
|
|||
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
|||
|
|
#[serde(rename_all = "camelCase")]
|
|||
|
|
pub struct AddConversationListenerParams {
|
|||
|
|
pub conversation_id: ConversationId,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
|||
|
|
#[serde(rename_all = "camelCase")]
|
|||
|
|
pub struct RemoveConversationListenerParams {
|
|||
|
|
pub subscription_id: Uuid,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
|||
|
|
#[serde(rename_all = "camelCase")]
|
|||
|
|
pub enum InputItem {
|
|||
|
|
Text {
|
|||
|
|
text: String,
|
|||
|
|
},
|
|||
|
|
/// Pre‑encoded data: URI image.
|
|||
|
|
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,
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#[allow(clippy::unwrap_used)]
|
|||
|
|
#[cfg(test)]
|
|||
|
|
mod tests {
|
|||
|
|
use super::*;
|
|||
|
|
use pretty_assertions::assert_eq;
|
|||
|
|
use serde_json::json;
|
|||
|
|
|
|||
|
|
#[test]
|
|||
|
|
fn serialize_new_conversation() {
|
|||
|
|
let request = CodexRequest::NewConversation {
|
|||
|
|
request_id: RequestId::Integer(42),
|
|||
|
|
params: NewConversationParams {
|
|||
|
|
model: Some("gpt-5".to_string()),
|
|||
|
|
profile: None,
|
|||
|
|
cwd: None,
|
|||
|
|
approval_policy: Some(CodexToolCallApprovalPolicy::OnRequest),
|
|||
|
|
sandbox: None,
|
|||
|
|
config: None,
|
|||
|
|
base_instructions: None,
|
|||
|
|
include_plan_tool: None,
|
|||
|
|
},
|
|||
|
|
};
|
|||
|
|
assert_eq!(
|
|||
|
|
json!({
|
|||
|
|
"method": "newConversation",
|
|||
|
|
"id": 42,
|
|||
|
|
"params": {
|
|||
|
|
"model": "gpt-5",
|
|||
|
|
"approvalPolicy": "on-request"
|
|||
|
|
}
|
|||
|
|
}),
|
|||
|
|
serde_json::to_value(&request).unwrap(),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
}
|