use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;
use std::time::Duration;
use super::logger::LoggerConfig;
pub use agent_client_protocol::{
ContentBlock, EnvVariable, Error, ImageContent, McpServer, Plan, SessionId, StopReason,
TextContent, ToolCall, ToolCallUpdate,
};
pub const PROTOCOL_VERSION: u32 = 1;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum PermissionMode {
#[serde(rename = "auto")]
Auto,
#[serde(rename = "manual")]
Manual,
#[serde(rename = "selective")]
Selective,
}
impl Default for PermissionMode {
fn default() -> Self {
PermissionMode::Auto
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum ToolCallStatus {
#[serde(rename = "pending")]
Pending,
#[serde(rename = "in_progress")]
InProgress,
#[serde(rename = "completed")]
Completed,
#[serde(rename = "failed")]
Failed,
#[serde(rename = "running")]
Running,
#[serde(rename = "finished")]
Finished,
#[serde(rename = "error")]
Error,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum PlanPriority {
#[serde(rename = "high")]
High,
#[serde(rename = "medium")]
Medium,
#[serde(rename = "low")]
Low,
}
impl Default for PlanPriority {
fn default() -> Self {
PlanPriority::Medium
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum PlanStatus {
#[serde(rename = "pending")]
Pending,
#[serde(rename = "in_progress")]
InProgress,
#[serde(rename = "completed")]
Completed,
}
impl Default for PlanStatus {
fn default() -> Self {
PlanStatus::Pending
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct PlanEntry {
pub content: String,
#[serde(default)]
pub priority: PlanPriority,
#[serde(default)]
pub status: PlanStatus,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum UserMessageChunk {
Text {
#[serde(rename = "text")]
content: String,
},
Path {
#[serde(rename = "path")]
path: PathBuf,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserMessage {
#[serde(rename = "type")]
pub message_type: String,
pub chunks: Vec<UserMessageChunk>,
}
impl UserMessage {
pub fn new_text(text: String) -> Self {
Self {
message_type: "user".to_string(),
chunks: vec![UserMessageChunk::Text { content: text }],
}
}
pub fn new_path(path: PathBuf) -> Self {
Self {
message_type: "user".to_string(),
chunks: vec![UserMessageChunk::Path { path }],
}
}
pub fn new(chunks: Vec<UserMessageChunk>) -> Self {
Self {
message_type: "user".to_string(),
chunks,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Icon {
#[serde(rename = "type")]
pub icon_type: String,
pub value: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolCallConfirmation {
#[serde(rename = "type")]
pub confirmation_type: String,
pub description: Option<String>,
pub command: Option<String>,
#[serde(rename = "rootCommand")]
pub root_command: Option<String>,
#[serde(rename = "serverName")]
pub server_name: Option<String>,
#[serde(rename = "toolName")]
pub tool_name: Option<String>,
#[serde(rename = "toolDisplayName")]
pub tool_display_name: Option<String>,
pub urls: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolCallContent {
#[serde(rename = "type")]
pub content_type: String,
pub markdown: Option<String>,
pub path: Option<String>,
#[serde(rename = "oldText")]
pub old_text: Option<String>,
#[serde(rename = "newText")]
pub new_text: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolCallLocation {
pub path: String,
#[serde(rename = "lineStart")]
pub line_start: Option<u32>,
#[serde(rename = "lineEnd")]
pub line_end: Option<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentInfo {
#[serde(rename = "agentId")]
pub agent_id: String,
#[serde(rename = "agentIndex")]
pub agent_index: Option<u32>,
#[serde(rename = "taskId")]
pub task_id: Option<String>,
pub timestamp: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolCallMessage {
#[serde(rename = "type")]
pub message_type: String,
pub id: String,
pub label: String,
pub icon: Icon,
pub status: ToolCallStatus,
#[serde(rename = "toolName")]
pub tool_name: Option<String>,
pub content: Option<ToolCallContent>,
pub locations: Option<Vec<ToolCallLocation>>,
pub confirmation: Option<ToolCallConfirmation>,
#[serde(rename = "agentId")]
pub agent_id: Option<String>,
#[serde(rename = "agentInfo")]
pub agent_info: Option<AgentInfo>,
}
impl ToolCallMessage {
pub fn new(id: String, label: String, icon: Icon, status: ToolCallStatus) -> Self {
Self {
message_type: "tool_call".to_string(),
id,
label,
icon,
status,
tool_name: None,
content: None,
locations: None,
confirmation: None,
agent_id: None,
agent_info: None,
}
}
}
#[derive(Debug, Clone)]
pub struct WebSocketConfig {
pub url: Option<String>,
pub reconnect_attempts: u32,
pub reconnect_interval: Duration,
}
impl Default for WebSocketConfig {
fn default() -> Self {
Self {
url: Some("ws://localhost:8090/acp?peer=iflow".to_string()),
reconnect_attempts: 3,
reconnect_interval: Duration::from_secs(5),
}
}
}
impl WebSocketConfig {
pub fn new(url: String) -> Self {
Self {
url: Some(url),
..Default::default()
}
}
pub fn auto_start() -> Self {
Self {
url: None,
..Default::default()
}
}
pub fn with_reconnect_settings(
url: String,
reconnect_attempts: u32,
reconnect_interval: Duration,
) -> Self {
Self {
url: Some(url),
reconnect_attempts,
reconnect_interval,
}
}
pub fn auto_start_with_reconnect_settings(
reconnect_attempts: u32,
reconnect_interval: Duration,
) -> Self {
Self {
url: None,
reconnect_attempts,
reconnect_interval,
}
}
}
#[derive(Debug, Clone)]
pub struct FileAccessConfig {
pub enabled: bool,
pub allowed_dirs: Option<Vec<PathBuf>>,
pub read_only: bool,
pub max_size: u64,
}
impl Default for FileAccessConfig {
fn default() -> Self {
Self {
enabled: false,
allowed_dirs: None,
read_only: false,
max_size: 10 * 1024 * 1024, }
}
}
#[derive(Debug, Clone)]
pub struct ProcessConfig {
pub auto_start: bool,
pub start_port: Option<u16>,
pub debug: bool,
}
impl Default for ProcessConfig {
fn default() -> Self {
Self {
auto_start: true,
start_port: None, debug: false,
}
}
}
impl ProcessConfig {
pub fn new() -> Self {
Self::default()
}
pub fn auto_start(mut self, auto_start: bool) -> Self {
self.auto_start = auto_start;
self
}
pub fn start_port(mut self, port: u16) -> Self {
self.start_port = Some(port);
self
}
pub fn debug(mut self, debug: bool) -> Self {
self.debug = debug;
self
}
pub fn manual_start(self) -> Self {
self.auto_start(false)
}
pub fn enable_auto_start(self) -> Self {
self.auto_start(true)
}
pub fn enable_debug(self) -> Self {
self.debug(true)
}
pub fn stdio_mode(mut self) -> Self {
self.start_port = None;
self
}
}
#[derive(Debug, Clone)]
pub struct LoggingConfig {
pub enabled: bool,
pub level: String,
pub logger_config: LoggerConfig,
}
impl Default for LoggingConfig {
fn default() -> Self {
Self {
enabled: false,
level: "INFO".to_string(),
logger_config: LoggerConfig::default(),
}
}
}
#[derive(Debug, Clone)]
pub struct IFlowOptions {
pub cwd: PathBuf,
pub mcp_servers: Vec<McpServer>,
pub timeout: f64,
pub metadata: HashMap<String, serde_json::Value>,
pub file_access: FileAccessConfig,
pub process: ProcessConfig,
pub auth_method_id: Option<String>,
pub logging: LoggingConfig,
pub websocket: Option<WebSocketConfig>,
pub permission_mode: PermissionMode,
}
impl Default for IFlowOptions {
fn default() -> Self {
Self {
cwd: std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")),
mcp_servers: Vec::new(),
timeout: 120.0,
metadata: HashMap::new(),
file_access: FileAccessConfig::default(),
process: ProcessConfig::default(),
auth_method_id: None,
logging: LoggingConfig::default(),
websocket: None,
permission_mode: PermissionMode::Auto,
}
}
}
impl IFlowOptions {
pub fn new() -> Self {
Self::default()
}
pub fn with_cwd(mut self, cwd: PathBuf) -> Self {
self.cwd = cwd;
self
}
pub fn with_timeout(mut self, timeout: f64) -> Self {
self.timeout = timeout;
self
}
pub fn with_mcp_servers(mut self, servers: Vec<McpServer>) -> Self {
self.mcp_servers = servers;
self
}
pub fn with_metadata(mut self, metadata: HashMap<String, serde_json::Value>) -> Self {
self.metadata = metadata;
self
}
pub fn with_file_access_config(mut self, config: FileAccessConfig) -> Self {
self.file_access = config;
self
}
pub fn with_process_config(mut self, config: ProcessConfig) -> Self {
self.process = config;
self
}
pub fn with_auto_start(mut self, auto_start: bool) -> Self {
self.process.auto_start = auto_start;
self
}
pub fn with_auth_method_id(mut self, method_id: String) -> Self {
self.auth_method_id = Some(method_id);
self
}
pub fn with_logging_config(mut self, config: LoggingConfig) -> Self {
self.logging = config;
self
}
pub fn with_websocket_config(mut self, config: WebSocketConfig) -> Self {
self.websocket = Some(config);
self
}
pub fn with_permission_mode(mut self, mode: PermissionMode) -> Self {
self.permission_mode = mode;
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ErrorMessageDetails {
pub code: i32,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub details: Option<std::collections::HashMap<String, serde_json::Value>>,
}
impl ErrorMessageDetails {
pub fn new(code: i32, message: String) -> Self {
Self {
code,
message,
details: None,
}
}
pub fn with_details(
code: i32,
message: String,
details: std::collections::HashMap<String, serde_json::Value>,
) -> Self {
Self {
code,
message,
details: Some(details),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum Message {
#[serde(rename = "user")]
User { content: String },
#[serde(rename = "assistant")]
Assistant { content: String },
#[serde(rename = "tool_call")]
ToolCall {
id: String,
name: String,
status: String,
},
#[serde(rename = "plan")]
Plan { entries: Vec<PlanEntry> },
#[serde(rename = "task_finish")]
TaskFinish { reason: Option<String> },
#[serde(rename = "error")]
Error {
code: i32,
message: String,
#[serde(skip_serializing_if = "Option::is_none")]
details: Option<std::collections::HashMap<String, serde_json::Value>>,
},
}
impl Message {
pub fn is_task_finish(&self) -> bool {
matches!(self, Message::TaskFinish { .. })
}
pub fn is_error(&self) -> bool {
matches!(self, Message::Error { .. })
}
pub fn get_text(&self) -> Option<&str> {
match self {
Message::User { content } => Some(content),
Message::Assistant { content } => Some(content),
_ => None,
}
}
pub fn error(code: i32, message: String) -> Self {
Message::Error {
code,
message,
details: None,
}
}
pub fn error_with_details(
code: i32,
message: String,
details: std::collections::HashMap<String, serde_json::Value>,
) -> Self {
Message::Error {
code,
message,
details: Some(details),
}
}
}