From a7db3e45c5eeda75dd596d357961a130fa9510f0 Mon Sep 17 00:00:00 2001 From: Graham Barber Date: Sun, 14 Sep 2025 20:32:55 -0700 Subject: [PATCH] stub letta completion command --- src-tauri/Cargo.lock | 50 +++++++++++++++++++++++++++++++++ src-tauri/Cargo.toml | 1 + src-tauri/src/letta/commands.rs | 10 +++++++ src-tauri/src/letta/mod.rs | 43 ++++++++++++++++++++++++++++ src-tauri/src/lib.rs | 1 + 5 files changed, 105 insertions(+) diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 1f56196..a61fcf5 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -1171,6 +1171,17 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "eventsource-stream" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74fef4569247a5f429d9156b9d0a2599914385dd189c539334c625d8099d90ab" +dependencies = [ + "futures-core", + "nom", + "pin-project-lite", +] + [[package]] name = "fastrand" version = "2.3.0" @@ -1341,6 +1352,12 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + [[package]] name = "futures-util" version = "0.3.31" @@ -2373,6 +2390,12 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.8.9" @@ -2403,6 +2426,7 @@ dependencies = [ "futures-util", "keyring", "reqwest", + "reqwest-eventsource", "serde", "serde_json", "strum", @@ -2512,6 +2536,16 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "num-conv" version = "0.1.0" @@ -3616,6 +3650,22 @@ dependencies = [ "webpki-roots", ] +[[package]] +name = "reqwest-eventsource" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "632c55746dbb44275691640e7b40c907c16a2dc1a5842aa98aaec90da6ec6bde" +dependencies = [ + "eventsource-stream", + "futures-core", + "futures-timer", + "mime", + "nom", + "pin-project-lite", + "reqwest", + "thiserror 1.0.69", +] + [[package]] name = "ring" version = "0.17.14" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 81b7167..2ebacd3 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -33,6 +33,7 @@ strum = {version = "0.27.2", features = ["derive"] } tauri-plugin-store = "2" tauri-plugin-http = "2" reqwest = { version = "0.12.23", features = ["json"] } +reqwest-eventsource = "0.6.0" [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] tauri-plugin-positioner = "2" diff --git a/src-tauri/src/letta/commands.rs b/src-tauri/src/letta/commands.rs index 51f36d3..500b6fb 100644 --- a/src-tauri/src/letta/commands.rs +++ b/src-tauri/src/letta/commands.rs @@ -53,3 +53,13 @@ pub async fn set_letta_agent_id( Ok(id) } + +#[tauri::command] +pub async fn start_llm_completion( + state: tauri::State<'_, AppState>, + message: String, +) -> Result<(), ()> { + state.letta_manager.start_completion(message).await; + + Ok(()) +} diff --git a/src-tauri/src/letta/mod.rs b/src-tauri/src/letta/mod.rs index ecbdfc2..46255cd 100644 --- a/src-tauri/src/letta/mod.rs +++ b/src-tauri/src/letta/mod.rs @@ -1,6 +1,8 @@ use std::sync::Arc; +use futures_util::StreamExt; use reqwest::Client; +use reqwest_eventsource::{Event, EventSource}; use serde::{Deserialize, Serialize}; use serde_json::json; use strum::{Display, EnumString}; @@ -91,4 +93,45 @@ impl LettaManager { Err(_) => Vec::new(), } } + + pub async fn start_completion(&self, msg: String) { + match self + .secrets_manager + .get_secret(crate::secrets::SecretName::LettaApiKey) + { + Ok(api_key) => { + let base_url = self.base_url.lock().await.to_owned(); + let agent_id = self.base_url.lock().await.to_owned(); + let req = self + .http_client + .post(format!("{base_url}/v1/agents/{agent_id}/messages/stream")) + .header("Authorization", format!("Bearer {api_key}")) + .header("Content-Type", "application/json") + .json(&json!({ + "messages": [ + { + "role": "user", + "content": [ msg ] + } + ], + "stream_tokens": true + })); + + let mut source = + EventSource::new(req).expect("could not convert request to event source"); + + while let Some(event) = source.next().await { + match event { + Ok(Event::Open) => println!("stream opened"), + Ok(Event::Message(msg)) => println!("got stream message: {:?}", msg), + Err(err) => { + eprintln!("got stream error: {}", err); + source.close(); + } + } + } + } + Err(err) => eprintln!("could not fetch Letta API key: {}", err), + } + } } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 536ad8f..8c20601 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -73,6 +73,7 @@ pub fn run() { letta::commands::set_letta_base_url, letta::commands::get_letta_agent_id, letta::commands::set_letta_agent_id, + letta::commands::start_llm_completion, secrets::commands::has_secret, secrets::commands::set_secret, secrets::commands::delete_secret, -- 2.51.2