# iFlow Rust SDK API 细化改进方案
## 1. 问题分析
通过对现有代码的分析,发现以下API一致性问题:
1. **方法命名不一致**:部分方法使用下划线命名,部分使用驼峰命名
2. **参数传递方式不统一**:有些方法使用可选参数,有些使用构建器模式
3. **返回类型不规范**:部分方法返回Result,部分直接返回值
4. **功能实现不完整**:部分工具方法未实现或实现不完整
## 2. 改进方案
### 2.1 统一方法命名规范
#### 当前问题
- IFlowOptions中的方法命名不一致,如`with_timeout`、`with_cwd`等使用下划线,但结构体字段使用驼峰命名
#### 改进方案
- 统一使用Rust社区标准的驼峰命名法(lower_snake_case)用于函数和方法
- 结构体字段保持驼峰命名(upper_camel_case)
#### 具体修改
```rust
// IFlowOptions中的方法保持现状,因为已经是Rust标准风格
impl IFlowOptions {
pub fn new() -> Self { ... }
pub fn with_timeout(mut self, timeout: f64) -> Self { ... }
pub fn with_cwd(mut self, cwd: PathBuf) -> Self { ... }
// 保持现有命名风格
}
```
### 2.2 统一参数传递方式
#### 当前问题
- IFlowOptions使用构建器模式,但部分方法参数传递方式不一致
#### 改进方案
- 统一使用构建器模式进行配置
- 对于复杂参数,使用结构体封装
#### 具体修改
```rust
// 为WebSocket连接参数创建专门的结构体
#[derive(Debug, Clone)]
pub struct WebSocketConfig {
pub url: String,
pub reconnect_attempts: u32,
pub reconnect_interval: Duration,
}
impl IFlowOptions {
// 添加WebSocket配置方法
pub fn with_websocket_config(mut self, config: WebSocketConfig) -> Self {
self.websocket_config = Some(config);
self
}
}
```
### 2.3 统一返回类型规范
#### 当前问题
- 部分方法返回`Result<T>`,部分返回`Result<T, IFlowError>`
- 错误处理方式不一致
#### 改进方案
- 统一使用`Result<T, IFlowError>`作为返回类型
- 提供更详细的错误信息
#### 具体修改
```rust
// 在error.rs中增强错误类型
#[derive(Debug, Error)]
pub enum IFlowError {
#[error("Connection error: {0}")]
Connection(String),
#[error("Authentication error: {0}")]
Authentication(String),
#[error("Protocol error: {0}")]
Protocol(String),
#[error("Transport error: {0}")]
Transport(String),
#[error("JSON parse error: {0}")]
JsonParse(#[from] serde_json::Error),
#[error("Timeout error: {0}")]
Timeout(String),
#[error("Not connected")]
NotConnected,
#[error("Invalid configuration: {0}")]
InvalidConfig(String),
}
```
### 2.4 改进未实现的功能
#### 当前问题
- IFlowClientHandler中的大部分工具方法返回`method_not_found`错误
#### 改进方案
- 实现基本的文件读写功能
- 实现终端相关功能的基础版本
#### 具体修改
```rust
// 在client.rs中实现文件读写功能
impl IFlowClientHandler {
async fn write_text_file(
&self,
args: agent_client_protocol::WriteTextFileRequest,
) -> anyhow::Result<agent_client_protocol::WriteTextFileResponse, agent_client_protocol::Error> {
use tokio::fs;
// 检查文件访问权限
// 这里需要访问IFlowClient的配置来检查权限
// 写入文件
fs::write(&args.path, &args.content)
.await
.map_err(|e| agent_client_protocol::Error::internal_error(e.to_string()))?;
Ok(agent_client_protocol::WriteTextFileResponse {
success: true,
})
}
async fn read_text_file(
&self,
args: agent_client_protocol::ReadTextFileRequest,
) -> anyhow::Result<agent_client_protocol::ReadTextFileResponse, agent_client_protocol::Error> {
use tokio::fs;
// 检查文件访问权限
// 这里需要访问IFlowClient的配置来检查权限
// 读取文件
let content = fs::read_to_string(&args.path)
.await
.map_err(|e| agent_client_protocol::Error::internal_error(e.to_string()))?;
Ok(agent_client_protocol::ReadTextFileResponse {
content,
})
}
}
```
### 2.5 提供具体的代码修改建议
#### 2.5.1 修改IFlowOptions结构体
在`src/types.rs`中:
```rust
// 添加WebSocket配置结构体
#[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 {
/// Create a new WebSocketConfig with the specified URL and default reconnect settings
pub fn new(url: String) -> Self {
Self {
url: Some(url),
..Default::default()
}
}
/// Create a new WebSocketConfig for auto-start mode (URL will be auto-generated)
pub fn auto_start() -> Self {
Self {
url: None,
..Default::default()
}
}
/// Create a new WebSocketConfig with custom reconnect settings
pub fn with_reconnect_settings(url: String, reconnect_attempts: u32, reconnect_interval: Duration) -> Self {
Self {
url: Some(url),
reconnect_attempts,
reconnect_interval,
}
}
/// Create a new WebSocketConfig for auto-start mode with custom reconnect settings
pub fn auto_start_with_reconnect_settings(reconnect_attempts: u32, reconnect_interval: Duration) -> Self {
Self {
url: None,
reconnect_attempts,
reconnect_interval,
}
}
}
// 修改IFlowOptions结构体
#[derive(Debug, Clone)]
pub struct IFlowOptions {
/// Current working directory
pub cwd: PathBuf,
/// MCP servers to connect to
pub mcp_servers: Vec<McpServer>,
/// Request timeout in seconds
pub timeout: f64,
/// Log level
pub log_level: String,
/// Additional metadata to include in requests
pub metadata: HashMap<String, serde_json::Value>,
/// Whether to allow file access
pub file_access: bool,
/// Allowed directories for file access
pub file_allowed_dirs: Option<Vec<PathBuf>>,
/// Whether file access is read-only
pub file_read_only: bool,
/// Maximum file size for file access
pub file_max_size: u64,
/// Whether to automatically start the iFlow process
pub auto_start_process: bool,
/// Port to start the iFlow process on (deprecated)
pub process_start_port: u16,
/// Authentication method ID
pub auth_method_id: Option<String>,
/// Logger configuration
pub log_config: LoggerConfig,
/// WebSocket configuration (if None, use stdio)
pub websocket_config: Option<WebSocketConfig>,
/// Permission mode for tool calls
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: 30.0,
log_level: "INFO".to_string(),
metadata: HashMap::new(),
file_access: false,
file_allowed_dirs: None,
file_read_only: false,
file_max_size: 10 * 1024 * 1024, // 10MB
auto_start_process: true,
process_start_port: 8090,
auth_method_id: None,
log_config: LoggerConfig::default(),
websocket_config: None,
permission_mode: PermissionMode::Auto,
}
}
}
// 修改相关方法
impl IFlowOptions {
/// Set WebSocket configuration for WebSocket connection
///
/// # Arguments
/// * `config` - The WebSocket configuration to use
pub fn with_websocket_config(mut self, config: WebSocketConfig) -> Self {
self.websocket_config = Some(config);
self
}
/// Set WebSocket URL for WebSocket connection (deprecated, use with_websocket_config instead)
///
/// # Arguments
/// * `url` - The WebSocket URL to connect to
#[deprecated(note = "Use with_websocket_config instead")]
pub fn with_websocket_url<S: Into<String>>(mut self, url: S) -> Self {
self.websocket_config = Some(WebSocketConfig {
url: url.into(),
..Default::default()
});
self
}
}
```
#### 2.5.2 增强错误处理
在`src/error.rs`中:
```rust
use thiserror::Error;
/// Custom error type for iFlow SDK
#[derive(Debug, Error)]
pub enum IFlowError {
/// Connection error
#[error("Connection error: {0}")]
Connection(String),
/// Authentication error
#[error("Authentication error: {0}")]
Authentication(String),
/// Protocol error
#[error("Protocol error: {0}")]
Protocol(String),
/// Transport error
#[error("Transport error: {0}")]
Transport(String),
/// JSON parse error
#[error("JSON parse error: {0}")]
JsonParse(#[from] serde_json::Error),
/// Timeout error
#[error("Timeout error: {0}")]
Timeout(String),
/// Not connected error
#[error("Not connected")]
NotConnected,
/// Invalid configuration error
#[error("Invalid configuration: {0}")]
InvalidConfig(String),
/// File access error
#[error("File access error: {0}")]
FileAccess(String),
/// Permission denied error
#[error("Permission denied: {0}")]
PermissionDenied(String),
}
/// Type alias for Result with IFlowError
pub type Result<T> = std::result::Result<T, IFlowError>;
```
#### 2.5.3 实现文件读写功能
在`src/client.rs`中修改`IFlowClientHandler`实现:
```rust
#[async_trait::async_trait(?Send)]
impl Client for IFlowClientHandler {
async fn write_text_file(
&self,
args: agent_client_protocol::WriteTextFileRequest,
) -> anyhow::Result<agent_client_protocol::WriteTextFileResponse, agent_client_protocol::Error>
{
use tokio::fs;
use std::path::Path;
// 检查文件路径是否在允许的目录中
// 注意:这里需要访问IFlowClient的配置来检查权限
// 由于当前实现限制,我们暂时允许所有文件操作
// 在完整实现中,应该从客户端传递权限配置
// 确保目录存在
if let Some(parent) = Path::new(&args.path).parent() {
fs::create_dir_all(parent)
.await
.map_err(|e| agent_client_protocol::Error::internal_error(e.to_string()))?;
}
// 写入文件
fs::write(&args.path, &args.content)
.await
.map_err(|e| agent_client_protocol::Error::internal_error(e.to_string()))?;
Ok(agent_client_protocol::WriteTextFileResponse {
success: true,
})
}
async fn read_text_file(
&self,
args: agent_client_protocol::ReadTextFileRequest,
) -> anyhow::Result<agent_client_protocol::ReadTextFileResponse, agent_client_protocol::Error>
{
use tokio::fs;
// 检查文件访问权限
// 注意:这里需要访问IFlowClient的配置来检查权限
// 在完整实现中,应该从客户端传递权限配置
// 读取文件
let content = fs::read_to_string(&args.path)
.await
.map_err(|e| agent_client_protocol::Error::internal_error(e.to_string()))?;
Ok(agent_client_protocol::ReadTextFileResponse {
content,
})
}
// ... 其他方法保持不变
}
```
#### 2.5.4 增强WebSocket连接处理
在`src/client.rs`中修改`connect_websocket`方法:
```rust
/// Connect to iFlow via WebSocket
async fn connect_websocket(&mut self) -> Result<()> {
info!("Connecting to iFlow via WebSocket");
let websocket_config = self.options.websocket_config.as_ref()
.ok_or_else(|| IFlowError::InvalidConfig("WebSocket configuration not provided".to_string()))?;
// Keep the process manager when auto-start is needed
let mut process_manager_to_keep: Option<IFlowProcessManager> = None;
// Check if we need to start iFlow process
let final_url = if self.options.auto_start_process && websocket_config.url.starts_with("ws://localhost:") {
info!("iFlow auto-start enabled, checking if iFlow is already running...");
// Try to connect first to see if iFlow is already running
let mut test_transport = WebSocketTransport::new(websocket_config.url.clone(), self.options.timeout);
if test_transport.connect().await.is_err() {
// iFlow not running, start it
info!("iFlow not running, starting process...");
let mut pm = IFlowProcessManager::new(self.options.process_start_port);
let iflow_url = pm.start(true).await?
.ok_or_else(|| IFlowError::Connection("Failed to start iFlow with WebSocket".to_string()))?;
info!("Started iFlow process at {}", iflow_url);
// Keep the process manager to avoid early handle drop causing child process exit due to stdout/stderr pipe issues
process_manager_to_keep = Some(pm);
iflow_url
} else {
let _ = test_transport.close().await;
websocket_config.url.clone()
}
} else {
websocket_config.url.clone()
};
// Create WebSocket transport with increased timeout
let mut transport = WebSocketTransport::new(final_url.clone(), self.options.timeout);
// Connect to WebSocket with retry logic
let mut connect_attempts = 0;
while connect_attempts < websocket_config.reconnect_attempts {
match transport.connect().await {
Ok(_) => {
info!("Successfully connected to WebSocket at {}", final_url);
break;
}
Err(e) => {
connect_attempts += 1;
tracing::warn!("Failed to connect to WebSocket (attempt {}): {}", connect_attempts, e);
if connect_attempts >= websocket_config.reconnect_attempts {
return Err(IFlowError::Connection(format!(
"Failed to connect to WebSocket after {} attempts: {}",
websocket_config.reconnect_attempts, e
)));
}
// Wait before retrying
tracing::info!("Waiting {:?} before retry...", websocket_config.reconnect_interval);
tokio::time::sleep(websocket_config.reconnect_interval).await;
}
}
}
// Create ACP protocol handler
let mut acp_protocol = ACPProtocol::new(transport, self.message_sender.clone(), self.options.timeout);
acp_protocol.set_permission_mode(self.options.permission_mode);
// Store the connection (now also holds process_manager)
self.connection = Some(Connection::WebSocket {
acp_protocol,
session_id: None,
process_manager: process_manager_to_keep,
});
*self.connected.lock().await = true;
info!("Connected to iFlow via WebSocket");
Ok(())
}
```
## 3. 向后兼容性保证
1. 保留现有的`with_websocket_url`方法,但标记为deprecated
2. 保持所有公共API的签名不变
3. 在主要版本更新前提供迁移指南
## 4. 实施计划
1. 第一阶段:修改IFlowOptions结构体和相关方法
2. 第二阶段:增强错误处理机制
3. 第三阶段:实现未完成的功能
4. 第四阶段:完善WebSocket连接处理
5. 第五阶段:添加测试用例和文档
## 5. 已完成任务
### 5.1 WebSocketConfig 默认参数实现
已完成WebSocketConfig的默认参数实现,现在用户可以使用更简洁的方式创建WebSocket配置:
```rust
// 使用默认参数创建WebSocket配置
let config = WebSocketConfig::new("ws://localhost:8090/acp?peer=iflow".to_string());
// 使用自定义重连参数创建WebSocket配置
let config = WebSocketConfig::with_reconnect_settings(
"ws://localhost:8090/acp?peer=iflow".to_string(),
5, // reconnect attempts
std::time::Duration::from_secs(10) // reconnect interval
);
// 在自动启动模式下,可以省略URL
let config = WebSocketConfig::auto_start();
// 在自动启动模式下使用自定义重连参数
let config = WebSocketConfig::auto_start_with_reconnect_settings(
5, // reconnect attempts
std::time::Duration::from_secs(10) // reconnect interval
);
// 使用默认配置
let config = WebSocketConfig::default();
```
相关文件修改:
1. `src/types.rs` - 添加了WebSocketConfig的new、auto_start、with_reconnect_settings和auto_start_with_reconnect_settings方法
2. `examples/permission_modes.rs` - 更新了WebSocket配置的使用方式
3. `examples/websocket_client.rs` - 更新了WebSocket配置的使用方式
4. `src/client.rs` - 更新了WebSocket连接逻辑以支持自动URL生成
5. `README.md` - 更新了WebSocket配置的文档示例