WIP push-to-talk Letta chat frontend

decode letta completion messages

graham.systems 1a77e3a5 7ae0d991

verified
+309 -29
+2
src-tauri/.cargo/config.toml
··· 1 + [env] 2 + TS_RS_EXPORT_DIR = { value = "../src/lib/rust", relative = true }
+53
src-tauri/Cargo.lock
··· 2429 2429 "reqwest-eventsource", 2430 2430 "serde", 2431 2431 "serde_json", 2432 + "serde_string_enum", 2432 2433 "strum", 2433 2434 "tauri", 2434 2435 "tauri-build", ··· 2440 2441 "tauri-plugin-window-state", 2441 2442 "tokio", 2442 2443 "tokio-tungstenite", 2444 + "ts-rs", 2443 2445 "tungstenite", 2446 + "unicase", 2444 2447 ] 2445 2448 2446 2449 [[package]] ··· 3983 3986 ] 3984 3987 3985 3988 [[package]] 3989 + name = "serde_string_enum" 3990 + version = "0.2.1" 3991 + source = "registry+https://github.com/rust-lang/crates.io-index" 3992 + checksum = "bc057e156c7ee0eecf104019415e2089fe55f4e8fc92d49059bcb22548b97028" 3993 + dependencies = [ 3994 + "proc-macro2", 3995 + "quote", 3996 + "serde", 3997 + "syn 2.0.106", 3998 + "unicase", 3999 + ] 4000 + 4001 + [[package]] 3986 4002 name = "serde_urlencoded" 3987 4003 version = "0.7.1" 3988 4004 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 4783 4799 ] 4784 4800 4785 4801 [[package]] 4802 + name = "termcolor" 4803 + version = "1.4.1" 4804 + source = "registry+https://github.com/rust-lang/crates.io-index" 4805 + checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" 4806 + dependencies = [ 4807 + "winapi-util", 4808 + ] 4809 + 4810 + [[package]] 4786 4811 name = "thiserror" 4787 4812 version = "1.0.69" 4788 4813 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 5154 5179 checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 5155 5180 5156 5181 [[package]] 5182 + name = "ts-rs" 5183 + version = "11.0.1" 5184 + source = "registry+https://github.com/rust-lang/crates.io-index" 5185 + checksum = "6ef1b7a6d914a34127ed8e1fa927eb7088903787bcded4fa3eef8f85ee1568be" 5186 + dependencies = [ 5187 + "thiserror 2.0.16", 5188 + "ts-rs-macros", 5189 + ] 5190 + 5191 + [[package]] 5192 + name = "ts-rs-macros" 5193 + version = "11.0.1" 5194 + source = "registry+https://github.com/rust-lang/crates.io-index" 5195 + checksum = "e9d4ed7b4c18cc150a6a0a1e9ea1ecfa688791220781af6e119f9599a8502a0a" 5196 + dependencies = [ 5197 + "proc-macro2", 5198 + "quote", 5199 + "syn 2.0.106", 5200 + "termcolor", 5201 + ] 5202 + 5203 + [[package]] 5157 5204 name = "tungstenite" 5158 5205 version = "0.27.0" 5159 5206 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 5234 5281 dependencies = [ 5235 5282 "unic-common", 5236 5283 ] 5284 + 5285 + [[package]] 5286 + name = "unicase" 5287 + version = "2.8.1" 5288 + source = "registry+https://github.com/rust-lang/crates.io-index" 5289 + checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" 5237 5290 5238 5291 [[package]] 5239 5292 name = "unicode-ident"
+3
src-tauri/Cargo.toml
··· 38 38 tauri-plugin-http = "2" 39 39 reqwest = { version = "0.12.23", features = ["json"] } 40 40 reqwest-eventsource = "0.6.0" 41 + ts-rs = "11.0.1" 42 + serde_string_enum = "0.2.1" 43 + unicase = "2.8.1" 41 44 42 45 [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] 43 46 tauri-plugin-positioner = "2"
-12
src-tauri/src/cartesia/stt.rs
··· 36 36 } 37 37 38 38 #[derive(Deserialize, Debug)] 39 - pub enum MessageType { 40 - #[serde(rename = "transcript")] 41 - Transcript, 42 - #[serde(rename = "error")] 43 - Error, 44 - #[serde(rename = "done")] 45 - Done, 46 - #[serde(rename = "flush_done")] 47 - FlushDone, 48 - } 49 - 50 - #[derive(Deserialize, Debug)] 51 39 #[serde(tag = "type", rename_all = "snake_case")] 52 40 pub enum TranscriptionMessage { 53 41 Transcript {
+21 -17
src-tauri/src/letta/mod.rs
··· 3 3 use futures_util::StreamExt; 4 4 use reqwest::Client; 5 5 use reqwest_eventsource::{Event, EventSource}; 6 - use serde::{Deserialize, Serialize}; 7 - use serde_json::json; 8 - use strum::{Display, EnumString}; 6 + use serde_json::{from_str, json}; 9 7 use tauri::async_runtime::Mutex; 10 8 use tauri::Wry; 11 9 use tauri_plugin_store::Store; 12 10 13 11 use crate::secrets::SecretsManager; 12 + use types::{LettaAgentInfo, LettaCompletionMessage, LettaConfigKey}; 14 13 15 14 pub mod commands; 16 - 17 - #[derive(Serialize, Deserialize, Clone, Display, EnumString)] 18 - #[strum(prefix = "letta")] 19 - pub enum LettaConfigKey { 20 - BaseUrl, 21 - AgentId, 22 - } 23 - 24 - #[derive(Serialize, Deserialize)] 25 - pub struct LettaAgentInfo { 26 - id: String, 27 - name: String, 28 - } 15 + pub mod types; 29 16 30 17 pub struct LettaManager { 31 18 http_client: Client, ··· 130 117 while let Some(event) = source.next().await { 131 118 match event { 132 119 Ok(Event::Open) => println!("stream opened"), 133 - Ok(Event::Message(msg)) => println!("got stream message: {:?}", msg), 120 + Ok(Event::Message(msg)) => { 121 + if msg.data == "[DONE]" { 122 + continue; 123 + }; 124 + 125 + match from_str::<LettaCompletionMessage>(&msg.data) { 126 + Ok(content) => { 127 + println!("parsed content: {:?}", content) 128 + } 129 + Err(err) => { 130 + eprintln!( 131 + "failed to parse message: {:?}, err: {}", 132 + msg.data.clone(), 133 + err 134 + ) 135 + } 136 + } 137 + } 134 138 Err(err) => { 135 139 eprintln!("got stream error: {}", err); 136 140 source.close();
+204
src-tauri/src/letta/types.rs
··· 1 + use serde::{Deserialize, Serialize}; 2 + use serde_string_enum::{DeserializeLabeledStringEnum, SerializeLabeledStringEnum}; 3 + use strum::{Display, EnumString}; 4 + use ts_rs::TS; 5 + 6 + #[derive(Serialize, Deserialize, Clone, Display, EnumString)] 7 + #[strum(prefix = "letta")] 8 + pub enum LettaConfigKey { 9 + BaseUrl, 10 + AgentId, 11 + } 12 + 13 + #[derive(Serialize, Deserialize, TS, Debug)] 14 + #[ts(export)] 15 + pub struct LettaAgentInfo { 16 + id: String, 17 + name: String, 18 + } 19 + 20 + #[derive(Serialize, Deserialize, TS, Debug)] 21 + #[serde(tag = "type", rename_all = "lowercase")] 22 + pub enum LettaMessageContent { 23 + Text { text: String }, 24 + Image { source: String }, 25 + } 26 + 27 + #[derive(TS, Debug, SerializeLabeledStringEnum, DeserializeLabeledStringEnum)] 28 + pub enum LettaReasoningSource { 29 + #[string = "reasoner_model"] 30 + ReasonerModel, 31 + 32 + #[string = "non_reasoner_model"] 33 + NonReasonerModel, 34 + } 35 + 36 + #[derive(SerializeLabeledStringEnum, DeserializeLabeledStringEnum, TS, Debug)] 37 + pub enum LettaHiddenReasoningState { 38 + #[string = "redacted"] 39 + Redacted, 40 + 41 + #[string = "omitted"] 42 + Omitted, 43 + } 44 + 45 + #[derive(Serialize, Deserialize, TS, Debug)] 46 + #[serde(untagged)] 47 + pub enum LettaToolCall { 48 + Call { 49 + name: String, 50 + arguments: String, 51 + tool_call_id: String, 52 + }, 53 + Delta { 54 + name: Option<String>, 55 + arguments: Option<String>, 56 + tool_call_id: Option<String>, 57 + }, 58 + } 59 + 60 + #[derive(SerializeLabeledStringEnum, DeserializeLabeledStringEnum, TS, Debug)] 61 + pub enum LettaToolReturnStatus { 62 + #[string = "success"] 63 + Success, 64 + 65 + #[string = "error"] 66 + Error, 67 + } 68 + 69 + #[derive(Serialize, Deserialize, TS, Debug)] 70 + #[serde(tag = "message_type", rename_all = "snake_case")] 71 + #[ts(export)] 72 + pub enum LettaCompletionMessage { 73 + SystemMessage { 74 + id: String, 75 + date: String, 76 + content: String, 77 + name: Option<String>, 78 + otid: Option<String>, 79 + sender_id: Option<String>, 80 + step_id: Option<String>, 81 + is_err: Option<bool>, 82 + seq_id: Option<i64>, 83 + run_id: Option<String>, 84 + }, 85 + UserMessage { 86 + id: String, 87 + date: String, 88 + content: Vec<LettaMessageContent>, 89 + name: Option<String>, 90 + otid: Option<String>, 91 + sender_id: Option<String>, 92 + step_id: Option<String>, 93 + is_err: Option<bool>, 94 + seq_id: Option<i64>, 95 + run_id: Option<String>, 96 + }, 97 + ReasoningMessage { 98 + id: String, 99 + date: String, 100 + reasoning: String, 101 + name: Option<String>, 102 + otid: Option<String>, 103 + sender_id: Option<String>, 104 + step_id: Option<String>, 105 + is_err: Option<bool>, 106 + seq_id: Option<i64>, 107 + run_id: Option<String>, 108 + source: Option<LettaReasoningSource>, 109 + signature: Option<String>, 110 + }, 111 + HiddenReasoningMessage { 112 + id: String, 113 + date: String, 114 + state: LettaHiddenReasoningState, 115 + name: Option<String>, 116 + otid: Option<String>, 117 + sender_id: Option<String>, 118 + step_id: Option<String>, 119 + is_err: Option<bool>, 120 + seq_id: Option<i64>, 121 + run_id: Option<String>, 122 + hidden_reasoning: Option<String>, 123 + }, 124 + ToolCallMessage { 125 + id: String, 126 + date: String, 127 + tool_call: LettaToolCall, 128 + name: Option<String>, 129 + otid: Option<String>, 130 + sender_id: Option<String>, 131 + step_id: Option<String>, 132 + is_err: Option<bool>, 133 + seq_id: Option<i64>, 134 + run_id: Option<String>, 135 + }, 136 + ToolReturnMessage { 137 + id: String, 138 + date: String, 139 + tool_return: String, 140 + status: LettaToolReturnStatus, 141 + tool_call_id: String, 142 + name: Option<String>, 143 + otid: Option<String>, 144 + sender_id: Option<String>, 145 + step_id: Option<String>, 146 + is_err: Option<bool>, 147 + seq_id: Option<i64>, 148 + run_id: Option<String>, 149 + stdout: Option<Vec<String>>, 150 + stderr: Option<Vec<String>>, 151 + }, 152 + AssistantMessage { 153 + id: String, 154 + date: String, 155 + content: Vec<LettaMessageContent>, 156 + name: Option<String>, 157 + otid: Option<String>, 158 + sender_id: Option<String>, 159 + step_id: Option<String>, 160 + is_err: Option<bool>, 161 + seq_id: Option<i64>, 162 + run_id: Option<String>, 163 + }, 164 + ApprovalRequestMessage { 165 + id: String, 166 + date: String, 167 + tool_call: LettaToolCall, 168 + name: Option<String>, 169 + otid: Option<String>, 170 + sender_id: Option<String>, 171 + step_id: Option<String>, 172 + is_err: Option<bool>, 173 + seq_id: Option<i64>, 174 + run_id: Option<String>, 175 + }, 176 + ApprovalResponseMessage { 177 + id: String, 178 + date: String, 179 + approve: bool, 180 + approval_request_id: String, 181 + name: Option<String>, 182 + otid: Option<String>, 183 + sender_id: Option<String>, 184 + step_id: Option<String>, 185 + is_err: Option<bool>, 186 + seq_id: Option<i64>, 187 + run_id: Option<String>, 188 + reason: Option<String>, 189 + }, 190 + StopReason { 191 + // Not documented, only observed value so far is "end_turn" 192 + stop_reason: String, 193 + }, 194 + UsageStatistics { 195 + completion_tokens: i64, 196 + prompt_tokens: i64, 197 + step_count: i64, 198 + // Observed but undocumented, inner type unclear 199 + // steps_messages: Option<Vec<String>>, 200 + 201 + // Observed but undocumented 202 + // run_ids: Option<Vec<String>>, 203 + }, 204 + }
+3
src/lib/rust/LettaAgentInfo.ts
··· 1 + // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. 2 + 3 + export type LettaAgentInfo = { id: string, name: string, };
+8
src/lib/rust/LettaCompletionMessage.ts
··· 1 + // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. 2 + import type { LettaHiddenReasoningState } from "./LettaHiddenReasoningState"; 3 + import type { LettaMessageContent } from "./LettaMessageContent"; 4 + import type { LettaReasoningSource } from "./LettaReasoningSource"; 5 + import type { LettaToolCall } from "./LettaToolCall"; 6 + import type { LettaToolReturnStatus } from "./LettaToolReturnStatus"; 7 + 8 + export type LettaCompletionMessage = { "message_type": "system_message", id: string, date: string, content: string, name: string | null, otid: string | null, sender_id: string | null, step_id: string | null, is_err: boolean | null, seq_id: bigint | null, run_id: string | null, } | { "message_type": "user_message", id: string, date: string, content: Array<LettaMessageContent>, name: string | null, otid: string | null, sender_id: string | null, step_id: string | null, is_err: boolean | null, seq_id: bigint | null, run_id: string | null, } | { "message_type": "reasoning_message", id: string, date: string, reasoning: string, name: string | null, otid: string | null, sender_id: string | null, step_id: string | null, is_err: boolean | null, seq_id: bigint | null, run_id: string | null, source: LettaReasoningSource | null, signature: string | null, } | { "message_type": "hidden_reasoning_message", id: string, date: string, state: LettaHiddenReasoningState, name: string | null, otid: string | null, sender_id: string | null, step_id: string | null, is_err: boolean | null, seq_id: bigint | null, run_id: string | null, hidden_reasoning: string | null, } | { "message_type": "tool_call_message", id: string, date: string, tool_call: LettaToolCall, name: string | null, otid: string | null, sender_id: string | null, step_id: string | null, is_err: boolean | null, seq_id: bigint | null, run_id: string | null, } | { "message_type": "tool_return_message", id: string, date: string, tool_return: string, status: LettaToolReturnStatus, tool_call_id: string, name: string | null, otid: string | null, sender_id: string | null, step_id: string | null, is_err: boolean | null, seq_id: bigint | null, run_id: string | null, stdout: Array<string> | null, stderr: Array<string> | null, } | { "message_type": "assistant_message", id: string, date: string, content: Array<LettaMessageContent>, name: string | null, otid: string | null, sender_id: string | null, step_id: string | null, is_err: boolean | null, seq_id: bigint | null, run_id: string | null, } | { "message_type": "approval_request_message", id: string, date: string, tool_call: LettaToolCall, name: string | null, otid: string | null, sender_id: string | null, step_id: string | null, is_err: boolean | null, seq_id: bigint | null, run_id: string | null, } | { "message_type": "approval_response_message", id: string, date: string, approve: boolean, approval_request_id: string, name: string | null, otid: string | null, sender_id: string | null, step_id: string | null, is_err: boolean | null, seq_id: bigint | null, run_id: string | null, reason: string | null, } | { "message_type": "stop_reason", stop_reason: string, } | { "message_type": "usage_statistics", completion_tokens: bigint, prompt_tokens: bigint, step_count: bigint, };
+3
src/lib/rust/LettaHiddenReasoningState.ts
··· 1 + // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. 2 + 3 + export type LettaHiddenReasoningState = "Redacted" | "Omitted";
+3
src/lib/rust/LettaMessageContent.ts
··· 1 + // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. 2 + 3 + export type LettaMessageContent = { "type": "text", text: string, } | { "type": "image", source: string, };
+3
src/lib/rust/LettaReasoningSource.ts
··· 1 + // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. 2 + 3 + export type LettaReasoningSource = "ReasonerModel" | "NonReasonerModel";
+3
src/lib/rust/LettaToolCall.ts
··· 1 + // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. 2 + 3 + export type LettaToolCall = { name: string, arguments: string, tool_call_id: string, } | { name: string | null, arguments: string | null, tool_call_id: string | null, };
+3
src/lib/rust/LettaToolReturnStatus.ts
··· 1 + // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. 2 + 3 + export type LettaToolReturnStatus = "Success" | "Error";