From 1343eda2dbabb38ecb865c602e361b24f43a5e54 Mon Sep 17 00:00:00 2001 From: limit_yan Date: Fri, 29 May 2026 22:32:15 +0800 Subject: [PATCH] [chore] Modernize Rust dependency management --- Cargo.toml | 115 +++++++++++++----- src/apps/cli/Cargo.toml | 22 ++-- src/apps/desktop/Cargo.toml | 31 ++--- src/apps/desktop/src/crash_diagnostics.rs | 10 +- src/apps/relay-server/Cargo.toml | 30 ++--- src/apps/relay-server/src/routes/websocket.rs | 7 +- src/apps/server/src/routes/websocket.rs | 4 +- src/crates/acp/Cargo.toml | 2 +- src/crates/core/Cargo.toml | 28 ++--- .../tools/browser_control/cdp_client.rs | 2 +- .../src/service/remote_connect/bot/feishu.rs | 20 ++- .../service/remote_connect/relay_client.rs | 15 ++- src/crates/core/src/service_agent_runtime.rs | 20 +-- src/crates/services-integrations/Cargo.toml | 11 +- .../services-integrations/src/git/graph.rs | 4 +- .../services-integrations/src/git/service.rs | 2 +- .../services-integrations/src/git/utils.rs | 4 +- .../src/mcp/protocol/client_info.rs | 25 ++-- .../src/mcp/protocol/transport_remote.rs | 90 ++++++++++---- src/crates/webdriver/Cargo.toml | 26 ++-- 20 files changed, 278 insertions(+), 190 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6bae8bcef..f32d8e3f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,21 +38,21 @@ edition = "2021" # Shared dependency versions to keep all crates aligned [workspace.dependencies] # Async runtime -tokio = { version = "1.0", features = ["full"] } -tokio-stream = "0.1" -tokio-util = "0.7" -async-trait = "0.1" -futures = "0.3" -futures-util = "0.3" +tokio = { version = "1.52", features = ["full"] } +tokio-stream = "0.1.18" +tokio-util = "0.7.18" +async-trait = "0.1.89" +futures = "0.3.31" +futures-util = "0.3.31" # Serialization -serde = { version = "1", features = ["derive"] } -serde_json = "1" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" serde_yaml = "0.9" # Error handling anyhow = "1.0" -thiserror = "1.0" +thiserror = "2" # Logging log = "0.4" @@ -66,38 +66,52 @@ chrono-tz = "0.10.4" cron = "0.15.0" regex = "1" base64 = "0.22" +# Keep macOS Tauri's dispatch2/bitflags expansion on the known-good bitflags release. +bitflags = "=2.11.1" image = { version = "0.25", default-features = false, features = ["png", "jpeg", "gif", "webp", "bmp"] } md5 = "0.7" -dashmap = "5" +dashmap = "6" indexmap = "2" include_dir = "0.7" +aes = "0.8" +hex = "0.4" # HTTP client -reqwest = { version = "0.12", default-features = false, features = ["native-tls", "rustls-tls-native-roots", "json", "stream", "multipart"] } +reqwest = { version = "0.13.4", default-features = false, features = ["native-tls", "rustls", "json", "stream", "multipart", "query", "form"] } # Debug Log HTTP Server -axum = { version = "0.7", features = ["json", "ws"] } -tower-http = { version = "0.6", features = ["cors", "fs"] } +axum = { version = "0.8", features = ["json", "ws"] } +tower-http = { version = "0.6.11", features = ["cors", "fs"] } # File system glob = "0.3" ignore = "0.4" -notify = "6.1" -dirs = "5.0" +notify = "8.2" +dirs = "6.0" dark-light = "1.1" dunce = "1" filetime = "0.2" fs2 = "0.4" -zip = "0.6" # plugin load +zip = { version = "4.6", default-features = false, features = ["deflate"] } # plugin load flate2 = "1.0" -toml = "0.8" +toml = "0.9" # Git -git2 = { version = "0.18", default-features = false, features = ["https", "vendored-libgit2"] } +git2 = { version = "0.21", default-features = false, features = ["https", "vendored-libgit2"] } # Terminal portable-pty = "0.8" vte = "0.15.0" +clap = { version = "4.6.1", features = ["derive"] } +crossterm = "0.28" +ratatui = "0.29" +unicode-width = "0.2" +pulldown-cmark = "0.11" +syntect = { version = "5", default-features = false, features = ["default-syntaxes", "default-themes", "regex-fancy"] } +syntect-tui = "3.0" +once_cell = "1" +libc = "0.2" +arboard = "3" # Grep (search) grep-searcher = "0.1" @@ -106,6 +120,7 @@ globset = "0.4" # SSE eventsource-stream = "0.2.3" +sse-stream = "0.2.3" # Command detection (cross-platform) which = "8.0" @@ -113,16 +128,39 @@ similar = "2.5" urlencoding = "2.1" # Tauri (desktop only) - tauri = { version = "2", features = ["unstable", "macos-private-api", "tray-icon"] } -tauri-plugin-opener = "2" -tauri-plugin-dialog = "2.6" -tauri-plugin-fs = "2" -tauri-plugin-log = "2" -tauri-plugin-autostart = "2" -tauri-plugin-notification = "2" -tauri-plugin-updater = "2" -tauri-plugin-global-shortcut = "2" -tauri-build = { version = "2", features = [] } +tauri = { version = "2.11", features = ["unstable", "macos-private-api", "tray-icon"] } +tauri-plugin-opener = "2.5" +tauri-plugin-dialog = "2.7" +tauri-plugin-fs = "2.5" +tauri-plugin-log = "2.8" +tauri-plugin-autostart = "2.5" +tauri-plugin-notification = "2.3" +tauri-plugin-updater = "2.10" +tauri-plugin-global-shortcut = "2.3" +tauri-build = { version = "2.6", features = [] } + +# Desktop support +screenshots = "0.8" +enigo = "0.2" +resvg = { version = "0.47", default-features = false } +atspi = "0.29" +leptess = "0.14" +core-foundation = "0.9" +core-graphics = { version = "0.23", features = ["elcapitan", "highsierra"] } +dispatch = "0.2" +block2 = "0.6" +objc2 = "0.6" +objc2-foundation = "0.3" +objc2-app-kit = "0.3" +objc2-vision = { version = "0.3.2", features = ["VNRecognizeTextRequest", "VNRequest", "VNObservation", "VNRequestHandler", "VNUtils", "VNTypes", "objc2-core-foundation"] } +objc2-web-kit = { version = "0.3", features = ["WKWebView", "WKSnapshotConfiguration", "WKPDFConfiguration", "block2", "objc2-app-kit"] } +tempfile = "3" +webview2-com = "0.38" +windows = "0.61" +windows-core = "0.61" +glib = "0.18" +gtk = "0.18" +webkit2gtk = "2.0" # Windows-specific dependencies win32job = "2.0" @@ -147,7 +185,26 @@ hostname = "0.4" qrcode = "0.14" # WebSocket client -tokio-tungstenite = { version = "0.24", features = ["rustls-tls-native-roots"] } +tokio-tungstenite = { version = "0.29", features = ["rustls-tls-native-roots"] } + +# MCP and remote runtimes +rmcp = { version = "1.7", default-features = false, features = [ + "auth", + "base64", + "client", + "elicitation", + "macros", + "reqwest", + "schemars", + "server", +] } +agent-client-protocol = { version = "0.12", features = ["unstable"] } +russh = "0.45" +russh-sftp = "2.1" +russh-keys = "0.45" +shellexpand = "3" +ssh_config = "0.1" +rustls = { version = "0.23", default-features = false } [profile.dev] incremental = true diff --git a/src/apps/cli/Cargo.toml b/src/apps/cli/Cargo.toml index d0418aa84..3eeb1f6f1 100644 --- a/src/apps/cli/Cargo.toml +++ b/src/apps/cli/Cargo.toml @@ -16,11 +16,11 @@ bitfun-events = { path = "../../crates/events" } bitfun-acp = { path = "../../crates/acp" } # CLI framework -clap = { version = "4", features = ["derive"] } +clap = { workspace = true } # TUI framework (Terminal User Interface) -ratatui = "0.28" -crossterm = "0.28" +ratatui = { workspace = true } +crossterm = { workspace = true } # Config and serialization dirs = { workspace = true } @@ -35,29 +35,29 @@ dashmap = { workspace = true } async-trait = { workspace = true } # Unicode width calculation (for correct wide-char handling) -unicode-width = "0.1" +unicode-width = { workspace = true } # Path canonicalization without Windows UNC prefix dunce = { workspace = true } # Markdown parsing and rendering -pulldown-cmark = "0.11" +pulldown-cmark = { workspace = true } # Diff computation for tool card rendering -similar = "2" +similar = { workspace = true } # Syntax highlighting for code blocks and tool cards -syntect = { version = "5", default-features = false, features = ["default-syntaxes", "default-themes", "regex-fancy"] } -syntect-tui = "3.0" +syntect = { workspace = true } +syntect-tui = { workspace = true } # Lazy initialization for syntax highlighter singleton -once_cell = "1" +once_cell = { workspace = true } # Unix-only best-effort terminal color detection (OSC 11) -libc = "0.2" +libc = { workspace = true } # Clipboard access (for reliable paste on Windows where bracketed paste is broken) -arboard = "3" +arboard = { workspace = true } # Inherited from workspace tokio = { workspace = true } diff --git a/src/apps/desktop/Cargo.toml b/src/apps/desktop/Cargo.toml index f4d1926f1..3f7408f61 100644 --- a/src/apps/desktop/Cargo.toml +++ b/src/apps/desktop/Cargo.toml @@ -51,27 +51,28 @@ ignore = { workspace = true } urlencoding = { workspace = true } reqwest = { workspace = true } zip = { workspace = true } -thiserror = "1.0" +thiserror = { workspace = true } futures = { workspace = true } async-trait = { workspace = true } sha1 = { workspace = true } -screenshots = "0.8" -enigo = "0.2" +screenshots = { workspace = true } +enigo = { workspace = true } image = { version = "0.24", default-features = false, features = ["png", "jpeg"] } -resvg = { version = "0.47.0", default-features = false } +resvg = { workspace = true } [target.'cfg(target_os = "macos")'.dependencies] -core-foundation = "0.9" -core-graphics = { version = "0.23", features = ["elcapitan", "highsierra"] } -dispatch = "0.2" -objc2 = { version = "0.6", features = ["exception"] } -objc2-foundation = "0.3" -objc2-app-kit = "0.3" -objc2-vision = { version = "0.3.2", features = ["VNRecognizeTextRequest", "VNRequest", "VNObservation", "VNRequestHandler", "VNUtils", "VNTypes", "objc2-core-foundation"] } +bitflags = { workspace = true } +core-foundation = { workspace = true } +core-graphics = { workspace = true } +dispatch = { workspace = true } +objc2 = { workspace = true, features = ["exception"] } +objc2-foundation = { workspace = true } +objc2-app-kit = { workspace = true } +objc2-vision = { workspace = true } [target.'cfg(windows)'.dependencies] win32job = { workspace = true } -windows = { version = "0.61.3", features = [ +windows = { workspace = true, features = [ "Foundation", "Globalization", "Graphics_Imaging", @@ -82,7 +83,7 @@ windows = { version = "0.61.3", features = [ "Win32_UI_Accessibility", "Win32_UI_WindowsAndMessaging", ] } -windows-core = "0.61.2" +windows-core = { workspace = true } [features] default = [] @@ -92,5 +93,5 @@ default = [] devtools = ["tauri/devtools"] [target.'cfg(target_os = "linux")'.dependencies] -atspi = "0.29" -leptess = "0.14.0" +atspi = { workspace = true } +leptess = { workspace = true } diff --git a/src/apps/desktop/src/crash_diagnostics.rs b/src/apps/desktop/src/crash_diagnostics.rs index 0b268c40b..10ce09afa 100644 --- a/src/apps/desktop/src/crash_diagnostics.rs +++ b/src/apps/desktop/src/crash_diagnostics.rs @@ -7,7 +7,7 @@ use std::fs::{self, File}; use std::io::{Read, Write}; use std::path::{Path, PathBuf}; use std::sync::OnceLock; -use zip::write::FileOptions; +use zip::write::SimpleFileOptions; const RUN_STATE_FILE: &str = "run-state.json"; const CRASH_REPORT_FILE: &str = "crash-report.json"; @@ -211,7 +211,7 @@ pub fn export_diagnostics_bundle() -> Result { let file = File::create(&bundle_path) .map_err(|error| format!("Failed to create diagnostics bundle: {}", error))?; let mut zip = zip::ZipWriter::new(file); - let options = FileOptions::default().compression_method(zip::CompressionMethod::Deflated); + let options = SimpleFileOptions::default().compression_method(zip::CompressionMethod::Deflated); let metadata = DiagnosticMetadata { exported_at: Utc::now().to_rfc3339(), @@ -309,7 +309,7 @@ fn add_directory_entries( zip: &mut zip::ZipWriter, dir: &Path, archive_prefix: &str, - options: FileOptions, + options: SimpleFileOptions, ) -> Result<(), String> { for entry in fs::read_dir(dir) .map_err(|error| format!("Failed to read directory {}: {}", dir.display(), error))? @@ -333,7 +333,7 @@ fn add_json_entry( zip: &mut zip::ZipWriter, archive_path: &str, value: &T, - options: FileOptions, + options: SimpleFileOptions, ) -> Result<(), String> { let content = serde_json::to_vec_pretty(value) .map_err(|error| format!("Failed to serialize {}: {}", archive_path, error))?; @@ -356,7 +356,7 @@ fn add_file_entry( zip: &mut zip::ZipWriter, source_path: &Path, archive_path: &str, - options: FileOptions, + options: SimpleFileOptions, ) -> Result<(), String> { let mut file = File::open(source_path) .map_err(|error| format!("Failed to open {}: {}", source_path.display(), error))?; diff --git a/src/apps/relay-server/Cargo.toml b/src/apps/relay-server/Cargo.toml index 46a8b8dff..bc01dca3f 100644 --- a/src/apps/relay-server/Cargo.toml +++ b/src/apps/relay-server/Cargo.toml @@ -15,28 +15,28 @@ path = "src/main.rs" [dependencies] # Web framework -axum = { version = "0.7", features = ["json", "ws"] } -tower-http = { version = "0.6", features = ["cors", "fs"] } +axum = { workspace = true } +tower-http = { workspace = true } # Async runtime -tokio = { version = "1.0", features = ["full"] } -futures-util = "0.3" +tokio = { workspace = true } +futures-util = { workspace = true } # Serialization -serde = { version = "1", features = ["derive"] } -serde_json = "1" +serde = { workspace = true } +serde_json = { workspace = true } # Error handling -anyhow = "1.0" +anyhow = { workspace = true } # Logging -tracing = "0.1" -tracing-subscriber = { version = "0.3", features = ["env-filter"] } +tracing = { workspace = true } +tracing-subscriber = { workspace = true } # Utilities -uuid = { version = "1.0", features = ["v4", "serde"] } -chrono = { version = "0.4", features = ["serde", "clock"] } -dashmap = "5.5" -rand = "0.8" -base64 = "0.21" -sha2 = "0.10" +uuid = { workspace = true } +chrono = { workspace = true } +dashmap = { workspace = true } +rand = { workspace = true } +base64 = { workspace = true } +sha2 = { workspace = true } diff --git a/src/apps/relay-server/src/routes/websocket.rs b/src/apps/relay-server/src/routes/websocket.rs index f1e6b5448..f55598b73 100644 --- a/src/apps/relay-server/src/routes/websocket.rs +++ b/src/apps/relay-server/src/routes/websocket.rs @@ -94,7 +94,12 @@ async fn handle_socket(socket: WebSocket, state: AppState) { let write_task = tokio::spawn(async move { while let Some(msg) = out_rx.recv().await { - if !msg.text.is_empty() && ws_sender.send(Message::Text(msg.text)).await.is_err() { + if !msg.text.is_empty() + && ws_sender + .send(Message::Text(msg.text.into())) + .await + .is_err() + { break; } } diff --git a/src/apps/server/src/routes/websocket.rs b/src/apps/server/src/routes/websocket.rs index a1bb1b5ab..7be149a27 100644 --- a/src/apps/server/src/routes/websocket.rs +++ b/src/apps/server/src/routes/websocket.rs @@ -74,7 +74,7 @@ async fn handle_socket(socket: WebSocket, state: AppState) { }; if let Ok(json) = serde_json::to_string(&welcome_msg) { - let _ = sender.send(Message::Text(json)).await; + let _ = sender.send(Message::Text(json.into())).await; } while let Some(msg) = receiver.next().await { @@ -141,7 +141,7 @@ async fn handle_text_message( }; let json = serde_json::to_string(&response)?; - sender.send(Message::Text(json)).await?; + sender.send(Message::Text(json.into())).await?; } WsMessage::Event { event, .. } => { tracing::debug!("Received event: {}", event); diff --git a/src/crates/acp/Cargo.toml b/src/crates/acp/Cargo.toml index 69ad62600..1c4645e36 100644 --- a/src/crates/acp/Cargo.toml +++ b/src/crates/acp/Cargo.toml @@ -12,7 +12,7 @@ name = "bitfun_acp" bitfun-core = { path = "../core", default-features = false, features = ["product-full"] } bitfun-events = { path = "../events" } -agent-client-protocol = { version = "=0.11.1", features = ["unstable"] } +agent-client-protocol = { workspace = true } tokio = { workspace = true } tokio-util = { workspace = true, features = ["compat"] } futures = { workspace = true } diff --git a/src/crates/core/Cargo.toml b/src/crates/core/Cargo.toml index 17c46580d..925cf8439 100644 --- a/src/crates/core/Cargo.toml +++ b/src/crates/core/Cargo.toml @@ -34,8 +34,8 @@ regex = { workspace = true } base64 = { workspace = true } image = { workspace = true, optional = true } md5 = { workspace = true, optional = true } -aes = { version = "0.8", optional = true } -hex = "0.4" +aes = { workspace = true, optional = true } +hex = { workspace = true } dashmap = { workspace = true, optional = true } indexmap = { workspace = true, optional = true } @@ -69,17 +69,11 @@ globset = { workspace = true, optional = true } eventsource-stream = { workspace = true, optional = true } -# MCP Streamable HTTP client (official rust-sdk used by Codex) -rmcp = { version = "0.12.0", default-features = false, features = [ - "auth", - "base64", - "client", - "macros", - "schemars", - "server", +# MCP Streamable HTTP client (official rust-sdk) +rmcp = { workspace = true, features = [ "transport-streamable-http-client-reqwest", ], optional = true } -sse-stream = { version = "0.2.1", optional = true } +sse-stream = { workspace = true, optional = true } # Shared AI protocol adapters bitfun-ai-adapters = { path = "../ai-adapters" } @@ -130,11 +124,11 @@ qrcode = { workspace = true, optional = true } tokio-tungstenite = { workspace = true, optional = true } # SSH - Remote SSH support (optional feature) -russh = { version = "0.45", optional = true } -russh-sftp = { version = "2.1", optional = true } -russh-keys = { version = "0.45", optional = true } -shellexpand = { version = "3", optional = true } -ssh_config = { version = "0.1", optional = true } +russh = { workspace = true, optional = true } +russh-sftp = { workspace = true, optional = true } +russh-keys = { workspace = true, optional = true } +shellexpand = { workspace = true, optional = true } +ssh_config = { workspace = true, optional = true } # Relay server shared library (embedded relay reuses standalone relay logic) bitfun-relay-server = { path = "../../apps/relay-server", optional = true } @@ -156,7 +150,7 @@ git2 = { workspace = true, features = ["vendored-openssl"], optional = true } [target.'cfg(windows)'.dependencies] win32job = { workspace = true } -rustls = { version = "0.23", default-features = false } +rustls = { workspace = true } rustls-native-certs = "0.8" schannel = "0.1" diff --git a/src/crates/core/src/agentic/tools/browser_control/cdp_client.rs b/src/crates/core/src/agentic/tools/browser_control/cdp_client.rs index 9ea915f38..e7d17ab32 100644 --- a/src/crates/core/src/agentic/tools/browser_control/cdp_client.rs +++ b/src/crates/core/src/agentic/tools/browser_control/cdp_client.rs @@ -163,7 +163,7 @@ impl CdpClient { debug!("CDP send id={} method={}", id, method); { let mut sink = self.sink.lock().await; - sink.send(Message::Text(msg.to_string())) + sink.send(Message::Text(msg.to_string().into())) .await .map_err(|e| BitFunError::tool(format!("CDP send failed: {}", e)))?; } diff --git a/src/crates/core/src/service/remote_connect/bot/feishu.rs b/src/crates/core/src/service/remote_connect/bot/feishu.rs index 7f9ef4cc3..4f6b65a0f 100644 --- a/src/crates/core/src/service/remote_connect/bot/feishu.rs +++ b/src/crates/core/src/service/remote_connect/bot/feishu.rs @@ -1120,7 +1120,7 @@ impl FeishuBot { let _ = write .write() .await - .send(WsMessage::Binary(pb::encode_frame(&resp_frame))) + .send(WsMessage::Binary(pb::encode_frame(&resp_frame).into())) .await; if let Some(parsed) = Self::parse_message_event_full(&event) { @@ -1235,7 +1235,11 @@ impl FeishuBot { } _ = ping_timer.tick() => { let ping = pb::Frame::new_ping(service_id); - let _ = write.write().await.send(WsMessage::Binary(pb::encode_frame(&ping))).await; + let _ = write + .write() + .await + .send(WsMessage::Binary(pb::encode_frame(&ping).into())) + .await; } } } @@ -1324,7 +1328,11 @@ impl FeishuBot { if let Ok(event) = serde_json::from_slice::(&frame.payload) { // Send ack let resp = pb::Frame::new_response(&frame, 200); - let _ = write.write().await.send(WsMessage::Binary(pb::encode_frame(&resp))).await; + let _ = write + .write() + .await + .send(WsMessage::Binary(pb::encode_frame(&resp).into())) + .await; if let Some(parsed) = Self::parse_message_event_full(&event) { let bot = self.clone(); @@ -1426,7 +1434,11 @@ impl FeishuBot { } _ = ping_timer.tick() => { let ping = pb::Frame::new_ping(service_id); - let _ = write.write().await.send(WsMessage::Binary(pb::encode_frame(&ping))).await; + let _ = write + .write() + .await + .send(WsMessage::Binary(pb::encode_frame(&ping).into())) + .await; } } } diff --git a/src/crates/core/src/service/remote_connect/relay_client.rs b/src/crates/core/src/service/remote_connect/relay_client.rs index cdfbdeb7d..dd4317107 100644 --- a/src/crates/core/src/service/remote_connect/relay_client.rs +++ b/src/crates/core/src/service/remote_connect/relay_client.rs @@ -165,7 +165,7 @@ impl RelayClient { tokio::spawn(async move { while let Some(msg) = cmd_rx.recv().await { if let Ok(json) = serde_json::to_string(&msg) { - if ws_write.send(Message::Text(json)).await.is_err() { + if ws_write.send(Message::Text(json.into())).await.is_err() { break; } } @@ -237,7 +237,8 @@ impl RelayClient { tokio::spawn(async move { while let Some(msg) = new_cmd_rx.recv().await { if let Ok(json) = serde_json::to_string(&msg) { - if new_write.send(Message::Text(json)).await.is_err() { + if new_write.send(Message::Text(json.into())).await.is_err() + { break; } } @@ -398,12 +399,10 @@ impl RelayClient { } async fn dial(ws_url: &str) -> Result { - let config = tokio_tungstenite::tungstenite::protocol::WebSocketConfig { - max_message_size: Some(64 * 1024 * 1024), - max_frame_size: Some(64 * 1024 * 1024), - max_write_buffer_size: 64 * 1024 * 1024, - ..Default::default() - }; + let config = tokio_tungstenite::tungstenite::protocol::WebSocketConfig::default() + .max_message_size(Some(64 * 1024 * 1024)) + .max_frame_size(Some(64 * 1024 * 1024)) + .max_write_buffer_size(64 * 1024 * 1024); #[cfg(windows)] { diff --git a/src/crates/core/src/service_agent_runtime.rs b/src/crates/core/src/service_agent_runtime.rs index e062dd1c8..8dfcb5c4e 100644 --- a/src/crates/core/src/service_agent_runtime.rs +++ b/src/crates/core/src/service_agent_runtime.rs @@ -11,6 +11,11 @@ use bitfun_runtime_ports::{ RemoteControlStateRequest, RemoteControlStateSnapshot, }; use bitfun_services_integrations::remote_connect::{ + build_remote_chat_messages, build_remote_model_catalog, + normalize_remote_model_selection as normalize_remote_model_selection_contract, + normalize_remote_session_model_id as normalize_remote_session_model_id_contract, + remote_dialog_submit_outcome_from_scheduler, + remote_model_selection_needs_config as remote_model_selection_needs_config_contract, ChatImageAttachment, ChatMessage, RemoteAssistantWorkspaceFacts, RemoteCancelRuntimeHost, RemoteChatHistoryRound, RemoteChatHistoryTextItem, RemoteChatHistoryThinkingItem, RemoteChatHistoryToolCall, RemoteChatHistoryToolItem, RemoteChatHistoryTurn, @@ -23,19 +28,14 @@ use bitfun_services_integrations::remote_connect::{ RemoteSessionMetadata, RemoteSessionRuntimeHost, RemoteSessionStateTracker, RemoteSessionTrackerHost, RemoteTerminalPrewarmRequest, RemoteWorkspaceFacts, RemoteWorkspaceFileRuntimeHost, RemoteWorkspaceKind as RemoteConnectWorkspaceKind, - RemoteWorkspaceRuntimeHost, RemoteWorkspaceUpdate, build_remote_chat_messages, - build_remote_model_catalog, - normalize_remote_model_selection as normalize_remote_model_selection_contract, - normalize_remote_session_model_id as normalize_remote_session_model_id_contract, - remote_dialog_submit_outcome_from_scheduler, - remote_model_selection_needs_config as remote_model_selection_needs_config_contract, + RemoteWorkspaceRuntimeHost, RemoteWorkspaceUpdate, }; use log::{debug, error, info}; use std::sync::Arc; use crate::agentic::coordination::{ - ConversationCoordinator, DialogQueuePriority, DialogScheduler, DialogSubmissionPolicy, - DialogSubmitOutcome, DialogTriggerSource, get_global_coordinator, get_global_scheduler, + get_global_coordinator, get_global_scheduler, ConversationCoordinator, DialogQueuePriority, + DialogScheduler, DialogSubmissionPolicy, DialogSubmitOutcome, DialogTriggerSource, }; use crate::agentic::image_analysis::ImageContextData; use crate::service::remote_connect::remote_server::RemoteExecutionDispatcher; @@ -67,7 +67,7 @@ fn git_branch_for_workspace_path(path: &std::path::Path) -> Option { git2::Repository::open(path).ok().and_then(|repo| { repo.head() .ok() - .and_then(|head| head.shorthand().map(String::from)) + .and_then(|head| head.shorthand().ok().map(String::from)) }) } @@ -193,8 +193,8 @@ fn remote_reasoning_mode_fact(reasoning_mode: ReasoningMode) -> RemoteReasoningM /// Falls back to the original if decoding/compression fails or the image is /// already within `max_bytes`. fn compress_remote_chat_data_url_for_mobile(data_url: &str, max_bytes: usize) -> String { - use base64::Engine; use base64::engine::general_purpose::STANDARD as BASE64; + use base64::Engine; use image::imageops::FilterType; const MAX_THUMBNAIL_DIM: u32 = 400; diff --git a/src/crates/services-integrations/Cargo.toml b/src/crates/services-integrations/Cargo.toml index f8e44f04d..09ed6c729 100644 --- a/src/crates/services-integrations/Cargo.toml +++ b/src/crates/services-integrations/Cargo.toml @@ -28,16 +28,9 @@ git2 = { workspace = true, optional = true } notify = { workspace = true, optional = true } rand = { workspace = true, optional = true } reqwest = { workspace = true, optional = true } -rmcp = { version = "0.12.0", default-features = false, features = [ - "auth", - "base64", - "client", - "macros", - "schemars", - "server", -], optional = true } +rmcp = { workspace = true, optional = true } sha2 = { workspace = true, optional = true } -sse-stream = { version = "0.2.1", optional = true } +sse-stream = { workspace = true, optional = true } thiserror = { workspace = true, optional = true } tokio-util = { workspace = true, optional = true } uuid = { workspace = true, optional = true } diff --git a/src/crates/services-integrations/src/git/graph.rs b/src/crates/services-integrations/src/git/graph.rs index 8ac75b043..0d01139e9 100644 --- a/src/crates/services-integrations/src/git/graph.rs +++ b/src/crates/services-integrations/src/git/graph.rs @@ -141,7 +141,7 @@ pub fn build_git_graph_for_branch( let mut nodes: Vec = Vec::new(); for (oid, commit) in commits { let hash = oid.to_string(); - let message = commit.summary().unwrap_or("").to_string(); + let message = commit.summary().ok().flatten().unwrap_or("").to_string(); let full_message = commit.message().unwrap_or("").to_string(); let author = commit.author(); @@ -215,7 +215,7 @@ fn collect_refs(repo: &Repository) -> Result>, git fn get_current_branch(repo: &Repository) -> Option { repo.head() .ok() - .and_then(|head| head.shorthand().map(|s| s.to_string())) + .and_then(|head| head.shorthand().ok().map(str::to_string)) } /// Allocates lanes (simplified algorithm). diff --git a/src/crates/services-integrations/src/git/service.rs b/src/crates/services-integrations/src/git/service.rs index 3ad46a42f..d89d4f0a9 100644 --- a/src/crates/services-integrations/src/git/service.rs +++ b/src/crates/services-integrations/src/git/service.rs @@ -37,7 +37,7 @@ impl GitService { .remotes() .map_err(|e| GitError::CommandFailed(e.to_string()))? .iter() - .filter_map(|name| name.map(|s| s.to_string())) + .filter_map(|name| name.ok().flatten().map(str::to_string)) .collect(); let path_str = path.as_ref().to_string_lossy().to_string(); diff --git a/src/crates/services-integrations/src/git/utils.rs b/src/crates/services-integrations/src/git/utils.rs index 1411507c6..160262656 100644 --- a/src/crates/services-integrations/src/git/utils.rs +++ b/src/crates/services-integrations/src/git/utils.rs @@ -30,7 +30,7 @@ pub fn get_repository_root>(path: P) -> Result pub fn get_current_branch(repo: &Repository) -> Result { match repo.head() { Ok(head) => { - if let Some(branch_name) = head.shorthand() { + if let Ok(branch_name) = head.shorthand() { Ok(branch_name.to_string()) } else { Ok("HEAD".to_string()) @@ -120,7 +120,7 @@ fn collect_statuses( let mut result = Vec::new(); for entry in statuses.iter() { - if let Some(path) = entry.path() { + if let Ok(path) = entry.path() { let status = entry.status(); let status_str = status_to_string(status); diff --git a/src/crates/services-integrations/src/mcp/protocol/client_info.rs b/src/crates/services-integrations/src/mcp/protocol/client_info.rs index e93246eae..cceb40bc2 100644 --- a/src/crates/services-integrations/src/mcp/protocol/client_info.rs +++ b/src/crates/services-integrations/src/mcp/protocol/client_info.rs @@ -1,28 +1,19 @@ //! MCP client identity and capability helper contracts. -use rmcp::model::{ - ClientCapabilities, ClientInfo, ElicitationCapability, Implementation, ProtocolVersion, -}; +use rmcp::model::{ClientCapabilities, ClientInfo, Implementation, ProtocolVersion}; pub fn create_mcp_client_info( client_name: impl Into, client_version: impl Into, ) -> ClientInfo { - ClientInfo { - protocol_version: ProtocolVersion::LATEST, - capabilities: ClientCapabilities::builder() + ClientInfo::new( + ClientCapabilities::builder() .enable_roots() .enable_sampling() - .enable_elicitation_with(ElicitationCapability { - schema_validation: Some(true), - }) + .enable_elicitation() + .enable_elicitation_schema_validation() .build(), - client_info: Implementation { - name: client_name.into(), - title: None, - version: client_version.into(), - icons: None, - website_url: None, - }, - } + Implementation::new(client_name, client_version), + ) + .with_protocol_version(ProtocolVersion::LATEST) } diff --git a/src/crates/services-integrations/src/mcp/protocol/transport_remote.rs b/src/crates/services-integrations/src/mcp/protocol/transport_remote.rs index 1c6b6d29e..5a5facde6 100644 --- a/src/crates/services-integrations/src/mcp/protocol/transport_remote.rs +++ b/src/crates/services-integrations/src/mcp/protocol/transport_remote.rs @@ -19,8 +19,8 @@ use reqwest::header::{ HeaderMap, HeaderName, HeaderValue, ACCEPT, CONTENT_TYPE, USER_AGENT, WWW_AUTHENTICATE, }; use rmcp::model::{ - CallToolRequestParam, ClientInfo, GetPromptRequestParam, JsonObject, LoggingLevel, - LoggingMessageNotificationParam, PaginatedRequestParam, ReadResourceRequestParam, + CallToolRequestParams, ClientInfo, GetPromptRequestParams, JsonObject, LoggingLevel, + LoggingMessageNotificationParam, PaginatedRequestParams, ReadResourceRequestParams, RequestNoParam, }; use rmcp::service::RunningService; @@ -138,6 +138,16 @@ impl BitFunStreamableHttpClient { } } +fn apply_custom_headers( + mut request_builder: reqwest::RequestBuilder, + custom_headers: HashMap, +) -> reqwest::RequestBuilder { + for (name, value) in custom_headers { + request_builder = request_builder.header(name, value); + } + request_builder +} + impl StreamableHttpClient for BitFunStreamableHttpClient { type Error = reqwest::Error; @@ -147,6 +157,7 @@ impl StreamableHttpClient for BitFunStreamableHttpClient { session_id: StdArc, last_event_id: Option, auth_token: Option, + custom_headers: HashMap, ) -> Result< futures::stream::BoxStream<'static, Result>, StreamableHttpError, @@ -163,12 +174,18 @@ impl StreamableHttpClient for BitFunStreamableHttpClient { if let Some(auth_header) = auth_token { request_builder = request_builder.bearer_auth(auth_header); } + request_builder = apply_custom_headers(request_builder, custom_headers); - let response = request_builder.send().await?; + let response = request_builder + .send() + .await + .map_err(StreamableHttpError::Client)?; if response.status() == reqwest::StatusCode::METHOD_NOT_ALLOWED { return Err(StreamableHttpError::ServerDoesNotSupportSse); } - let response = response.error_for_status()?; + let response = response + .error_for_status() + .map_err(StreamableHttpError::Client)?; match response.headers().get(CONTENT_TYPE) { Some(ct) => { @@ -194,21 +211,26 @@ impl StreamableHttpClient for BitFunStreamableHttpClient { uri: StdArc, session: StdArc, auth_token: Option, + custom_headers: HashMap, ) -> Result<(), StreamableHttpError> { let auth_token = self.resolve_auth_token(auth_token).await?; let mut request_builder = self.client.delete(uri.as_ref()); if let Some(auth_header) = auth_token { request_builder = request_builder.bearer_auth(auth_header); } + request_builder = apply_custom_headers(request_builder, custom_headers); let response = request_builder .header(HEADER_SESSION_ID, session.as_ref()) .send() - .await?; + .await + .map_err(StreamableHttpError::Client)?; if response.status() == reqwest::StatusCode::METHOD_NOT_ALLOWED { return Ok(()); } - let _ = response.error_for_status()?; + let _ = response + .error_for_status() + .map_err(StreamableHttpError::Client)?; Ok(()) } @@ -218,6 +240,7 @@ impl StreamableHttpClient for BitFunStreamableHttpClient { message: rmcp::model::ClientJsonRpcMessage, session_id: Option>, auth_token: Option, + custom_headers: HashMap, ) -> Result> { let auth_token = self.resolve_auth_token(auth_token).await?; let mut request = self @@ -230,8 +253,13 @@ impl StreamableHttpClient for BitFunStreamableHttpClient { if let Some(session_id) = session_id { request = request.header(HEADER_SESSION_ID, session_id.as_ref()); } + request = apply_custom_headers(request, custom_headers); - let response = request.json(&message).send().await?; + let response = request + .json(&message) + .send() + .await + .map_err(StreamableHttpError::Client)?; if response.status() == reqwest::StatusCode::UNAUTHORIZED { if let Some(header) = response.headers().get(WWW_AUTHENTICATE) { @@ -243,14 +271,16 @@ impl StreamableHttpClient for BitFunStreamableHttpClient { )) })? .to_string(); - return Err(StreamableHttpError::AuthRequired(AuthRequiredError { - www_authenticate_header: header, - })); + return Err(StreamableHttpError::AuthRequired(AuthRequiredError::new( + header, + ))); } } let status = response.status(); - let response = response.error_for_status()?; + let response = response + .error_for_status() + .map_err(StreamableHttpError::Client)?; if matches!( status, @@ -277,13 +307,17 @@ impl StreamableHttpClient for BitFunStreamableHttpClient { Ok(StreamableHttpPostResponse::Sse(event_stream, session_id)) } Some(ct) if ct.as_bytes().starts_with(JSON_MIME_TYPE.as_bytes()) => { - let message: rmcp::model::ServerJsonRpcMessage = response.json().await?; + let message: rmcp::model::ServerJsonRpcMessage = + response.json().await.map_err(StreamableHttpError::Client)?; Ok(StreamableHttpPostResponse::Json(message, session_id)) } _ => { // Compatibility: some servers return 200 with an empty body but omit Content-Type. // Treat this as Accepted for notifications (e.g. notifications/initialized). - let bytes = response.bytes().await?; + let bytes = response + .bytes() + .await + .map_err(StreamableHttpError::Client)?; let trimmed = bytes .iter() .copied() @@ -547,7 +581,7 @@ impl RemoteMCPTransport { let service = self.service().await?; let fut = service .peer() - .list_resources(Some(PaginatedRequestParam { cursor })); + .list_resources(Some(PaginatedRequestParams::default().with_cursor(cursor))); let result = Self::await_with_optional_timeout( self.request_timeout, fut, @@ -567,9 +601,9 @@ impl RemoteMCPTransport { pub async fn read_resource(&self, uri: &str) -> MCPRuntimeResult { let service = self.service().await?; - let fut = service.peer().read_resource(ReadResourceRequestParam { - uri: uri.to_string(), - }); + let fut = service + .peer() + .read_resource(ReadResourceRequestParams::new(uri.to_string())); let result = Self::await_with_optional_timeout( self.request_timeout, fut, @@ -593,7 +627,7 @@ impl RemoteMCPTransport { let service = self.service().await?; let fut = service .peer() - .list_prompts(Some(PaginatedRequestParam { cursor })); + .list_prompts(Some(PaginatedRequestParams::default().with_cursor(cursor))); let result = Self::await_with_optional_timeout( self.request_timeout, fut, @@ -622,10 +656,11 @@ impl RemoteMCPTransport { obj }); - let fut = service.peer().get_prompt(GetPromptRequestParam { - name: name.to_string(), - arguments, - }); + let mut params = GetPromptRequestParams::new(name.to_string()); + if let Some(arguments) = arguments { + params = params.with_arguments(arguments); + } + let fut = service.peer().get_prompt(params); let result = Self::await_with_optional_timeout( self.request_timeout, fut, @@ -648,7 +683,7 @@ impl RemoteMCPTransport { let service = self.service().await?; let fut = service .peer() - .list_tools(Some(PaginatedRequestParam { cursor })); + .list_tools(Some(PaginatedRequestParams::default().with_cursor(cursor))); let result = Self::await_with_optional_timeout( self.request_timeout, fut, @@ -681,10 +716,11 @@ impl RemoteMCPTransport { } }; - let fut = service.peer().call_tool(CallToolRequestParam { - name: name.to_string().into(), - arguments, - }); + let mut params = CallToolRequestParams::new(name.to_string()); + if let Some(arguments) = arguments { + params = params.with_arguments(arguments); + } + let fut = service.peer().call_tool(params); let result = Self::await_with_optional_timeout( self.request_timeout, fut, diff --git a/src/crates/webdriver/Cargo.toml b/src/crates/webdriver/Cargo.toml index 9e22dee6a..6ca79045f 100644 --- a/src/crates/webdriver/Cargo.toml +++ b/src/crates/webdriver/Cargo.toml @@ -18,20 +18,20 @@ base64 = { workspace = true } image = { workspace = true } [target.'cfg(target_os = "macos")'.dependencies] -block2 = "0.6" -objc2 = "0.6" -objc2-app-kit = { version = "0.3", features = ["NSImage", "NSImageRep", "NSBitmapImageRep"] } -objc2-foundation = { version = "0.3", features = ["NSString", "NSData", "NSError", "NSDictionary"] } -objc2-web-kit = { version = "0.3", features = ["WKWebView", "WKSnapshotConfiguration", "WKPDFConfiguration", "block2", "objc2-app-kit"] } +block2 = { workspace = true } +objc2 = { workspace = true } +objc2-app-kit = { workspace = true, features = ["NSImage", "NSImageRep", "NSBitmapImageRep"] } +objc2-foundation = { workspace = true, features = ["NSString", "NSData", "NSError", "NSDictionary"] } +objc2-web-kit = { workspace = true } [target.'cfg(target_os = "windows")'.dependencies] -tempfile = "3" -webview2-com = "0.38.2" -windows = { version = "0.61.3", features = ["Win32_Foundation", "Win32_System_Com", "Win32_System_Com_StructuredStorage"] } -windows-core = "0.61.2" +tempfile = { workspace = true } +webview2-com = { workspace = true } +windows = { workspace = true, features = ["Win32_Foundation", "Win32_System_Com", "Win32_System_Com_StructuredStorage"] } +windows-core = { workspace = true } [target.'cfg(target_os = "linux")'.dependencies] -glib = "0.18.5" -gtk = "0.18.2" -tempfile = "3" -webkit2gtk = "2.0.2" +glib = { workspace = true } +gtk = { workspace = true } +tempfile = { workspace = true } +webkit2gtk = { workspace = true }