Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5,038 changes: 4,799 additions & 239 deletions desktop/src-tauri/Cargo.lock

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions desktop/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ windows-sys = { version = "0.61", features = ["Win32_Storage_FileSystem"] }

[dependencies]
atomic-write-file = "0.3"
anyhow = "1"
dirs = "6"
tauri = { version = "2", features = [] }
tauri-plugin-deep-link = "2"
Expand Down Expand Up @@ -56,6 +57,7 @@ sprout-auth = { path = "../../crates/sprout-auth" }
sprout-core = { path = "../../crates/sprout-core" }
sprout-persona = { path = "../../crates/sprout-persona" }
sprout-sdk = { path = "../../crates/sprout-sdk" }
mesh-llm-host-runtime = { git = "https://github.com/Mesh-LLM/mesh-llm.git", rev = "fd949ebdfc8d2abba9792cbad0be744d55366300", package = "mesh-llm-host-runtime", default-features = false }
iroh-base = { version = "=1.0.0-rc.0", features = ["key"] }
base64 = "0.22"
sha2 = "0.11"
Expand Down
7 changes: 6 additions & 1 deletion desktop/src-tauri/src/app_state.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use std::{
collections::HashMap,
io::Write,
sync::{atomic::AtomicU16, Arc, Mutex},
sync::{Arc, Mutex, atomic::AtomicU16},
};

use nostr::{Keys, ToBech32};
use tauri::{AppHandle, Manager};
use tokio::sync::Mutex as AsyncMutex;

use crate::huddle::HuddleState;
use crate::managed_agents::ManagedAgentProcess;
Expand Down Expand Up @@ -33,6 +34,9 @@ pub struct AppState {
pub media_proxy_port: AtomicU16,
/// IOKit power assertion state — prevents idle sleep while agents run.
pub prevent_sleep: Arc<Mutex<crate::prevent_sleep::PreventSleepState>>,
/// In-process mesh-llm runtime started by Sprout. This is deliberately not
/// a sidecar process; the handle owns the embedded serve task and local API.
pub mesh_llm_runtime: AsyncMutex<Option<crate::mesh_llm::runtime::SproutMeshRuntime>>,
}

pub fn build_app_state() -> AppState {
Expand Down Expand Up @@ -78,6 +82,7 @@ pub fn build_app_state() -> AppState {
prevent_sleep: Arc::new(Mutex::new(
crate::prevent_sleep::PreventSleepState::default(),
)),
mesh_llm_runtime: AsyncMutex::new(None),
}
}

Expand Down
56 changes: 54 additions & 2 deletions desktop/src-tauri/src/commands/mesh_llm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,59 @@ pub async fn mesh_relay_iroh_url(
.map_err(|e| e.to_string())
}

/// Start a local mesh-llm node inside the Sprout process.
///
/// This exposes the same localhost OpenAI-compatible API shape as
/// `mesh-llm serve`, but it calls the mesh-llm Rust SDK directly instead of
/// launching a sidecar binary.
#[tauri::command]
pub async fn mesh_start_node(
app: AppHandle,
state: State<'_, AppState>,
request: mesh_llm::runtime::StartMeshNodeRequest,
) -> CmdResult<mesh_llm::runtime::MeshNodeStatus> {
let prefs = mesh_llm::offer::load_prefs(&app).map_err(|e| e.to_string())?;
let mut runtime = state.mesh_llm_runtime.lock().await;
if runtime.is_some() {
return Err("mesh-llm node is already running".to_string());
}

let started = mesh_llm::runtime::SproutMeshRuntime::start(request, &prefs)
.await
.map_err(|e| e.to_string())?;
let status = match started.status().await {
Ok(status) => status,
Err(error) => {
let _ = started.stop().await;
return Err(error.to_string());
}
};
*runtime = Some(started);
Ok(status)
}

/// Stop the in-process mesh-llm node if Sprout started one.
#[tauri::command]
pub async fn mesh_stop_node(state: State<'_, AppState>) -> CmdResult<()> {
let runtime = state.mesh_llm_runtime.lock().await.take();
if let Some(runtime) = runtime {
runtime.stop().await.map_err(|e| e.to_string())?;
}
Ok(())
}

/// Return the embedded node status, including the local OpenAI API URL.
#[tauri::command]
pub async fn mesh_node_status(
state: State<'_, AppState>,
) -> CmdResult<mesh_llm::runtime::MeshNodeStatus> {
let runtime = state.mesh_llm_runtime.lock().await;
match runtime.as_ref() {
Some(runtime) => runtime.status().await.map_err(|e| e.to_string()),
None => Ok(mesh_llm::runtime::stopped_status()),
}
}

// ── Publisher ──────────────────────────────────────────────────────────────

/// Result of `mesh_publish_offer` — surface enough state so the frontend
Expand Down Expand Up @@ -111,8 +164,7 @@ pub async fn mesh_publish_offer(
) -> CmdResult<PublishOfferResult> {
// Load prefs + endpoint key. These are sync; complete before any await.
let prefs = mesh_llm::offer::load_prefs(&app).map_err(|e| e.to_string())?;
let endpoint_key =
mesh_llm::load_or_create_endpoint_key(&app).map_err(|e| e.to_string())?;
let endpoint_key = mesh_llm::load_or_create_endpoint_key(&app).map_err(|e| e.to_string())?;
let endpoint_id_str = endpoint_key.public().to_string();

let d_tag = prefs.d_tag.clone();
Expand Down
3 changes: 3 additions & 0 deletions desktop/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,9 @@ pub fn run() {
mesh_get_sharing_prefs,
mesh_set_sharing_prefs,
mesh_relay_iroh_url,
mesh_start_node,
mesh_stop_node,
mesh_node_status,
mesh_publish_offer,
discover_acp_providers,
discover_managed_agent_prereqs,
Expand Down
1 change: 1 addition & 0 deletions desktop/src-tauri/src/mesh_llm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ pub mod endpoint;
pub mod nip11;
pub mod nip98;
pub mod offer;
pub mod runtime;

// Wildcard re-exports are deliberately avoided so that adding an
// unused-by-design helper to a submodule (e.g. a publisher that hasn't been
Expand Down
Loading
Loading