diff --git a/.gitea/workflows/release.yaml b/.gitea/workflows/release.yaml new file mode 100644 index 0000000..52dae45 --- /dev/null +++ b/.gitea/workflows/release.yaml @@ -0,0 +1,26 @@ +name: publish release +on: [ push ] +jobs: + release: + runs-on: ubuntu-latest + steps: + - name: Cache + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + - name: checkout + uses: actions/checkout@v6 + - name: toolchain + uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + targets: x86_64-pc-windows-gnu + - run: cargo build --release + - name: Release + uses: softprops/action-gh-release@v3 + with: + files: target/release/*.exe diff --git a/common/src/lib.rs b/common/src/lib.rs index 37f9925..246641e 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -2,4 +2,4 @@ pub mod config_loader; mod exit_error; pub use config_loader::config_from_file; -pub use exit_error::exit_with_error_message; \ No newline at end of file +pub use exit_error::exit_with_error_message; diff --git a/own_mcp/src/audio/client.rs b/own_mcp/src/audio/client.rs index 2fba19f..b41c301 100644 --- a/own_mcp/src/audio/client.rs +++ b/own_mcp/src/audio/client.rs @@ -41,7 +41,10 @@ type AudioResult = Result; impl AudioClient { fn get(&self, url: Url) -> reqwest::RequestBuilder { - self.client.get(url).header("Authorization", self.authorization.clone().unwrap_or(String::new())) + self.client.get(url).header( + "Authorization", + self.authorization.clone().unwrap_or(String::new()), + ) } } diff --git a/own_mcp/src/audio/mod.rs b/own_mcp/src/audio/mod.rs index b963b73..c3f8c5d 100644 --- a/own_mcp/src/audio/mod.rs +++ b/own_mcp/src/audio/mod.rs @@ -1,4 +1,4 @@ -pub mod recording; pub mod client; pub mod models; -pub use client::{AudioClient, AudioClientTrait}; \ No newline at end of file +pub mod recording; +pub use client::{AudioClient, AudioClientTrait}; diff --git a/own_mcp/src/audio/models.rs b/own_mcp/src/audio/models.rs index 02067a7..c89e21e 100644 --- a/own_mcp/src/audio/models.rs +++ b/own_mcp/src/audio/models.rs @@ -20,14 +20,14 @@ pub struct TranscriptionSegment { pub temperature: f32, pub avg_logprob: f32, pub compression_ratio: f32, - pub no_speech_prob: f32 + pub no_speech_prob: f32, } #[derive(Debug, Clone, Deserialize)] pub struct TranscriptionResponse { pub text: String, pub segments: Vec, - pub language: String + pub language: String, } #[derive(Debug, Clone, Serialize)] @@ -57,4 +57,4 @@ impl Default for SynthesisConfig { pub struct VoiceRequest { pub text: String, pub config: Option, -} \ No newline at end of file +} diff --git a/own_mcp/src/audio/recording.rs b/own_mcp/src/audio/recording.rs index a828f4a..afb3bb8 100644 --- a/own_mcp/src/audio/recording.rs +++ b/own_mcp/src/audio/recording.rs @@ -1,12 +1,12 @@ use cpal::traits::{DeviceTrait, StreamTrait}; use cpal::{BuildStreamError, Device, SampleFormat, SizedSample, Stream, SupportedStreamConfig}; -use std::fmt::Debug; -use std::sync::{Arc, Mutex}; -use std::{path, thread}; -use std::thread::JoinHandle; -use std::time::Duration; use hound::WavSpec; use log::{debug, error, info, trace}; +use std::fmt::Debug; +use std::sync::{Arc, Mutex}; +use std::thread::JoinHandle; +use std::time::Duration; +use std::{path, thread}; use thiserror::Error; fn generic_sample_to_i16(sample: impl SizedSample + Into, format: SampleFormat) -> i16 { @@ -136,7 +136,7 @@ pub fn start( let thread_samples = samples.clone(); let thread_recording_stop = recording_should_stop.clone(); let thread_device_config = device_config.clone(); - let record_thread_handle = thread::spawn(move || + let record_thread_handle = thread::spawn(move || { start_recording_blocking_with_parameters( µphone, &thread_device_config, @@ -144,7 +144,7 @@ pub fn start( thread_samples, thread_recording_stop, ) - ); + }); let spec = WavSpec { channels: device_config.channels(), @@ -163,25 +163,40 @@ pub fn start( }))) } -pub fn stop_and_take_data(handler: Arc>>) -> Result<(Vec, WavSpec), RecordingError> { - let handler = handler.lock().map_err(|_|RecordingError::ThreadPoison)?.take().ok_or(RecordingError::RecordingAlreadyStopped)?; +pub fn stop_and_take_data( + handler: Arc>>, +) -> Result<(Vec, WavSpec), RecordingError> { + let handler = handler + .lock() + .map_err(|_| RecordingError::ThreadPoison)? + .take() + .ok_or(RecordingError::RecordingAlreadyStopped)?; let samples = handler.samples.clone(); let spec = handler.spec.clone(); handler.stop_recording()?; - Ok((samples.lock().map_err(|_|RecordingError::ThreadPoison)?.to_vec(), spec)) + Ok(( + samples + .lock() + .map_err(|_| RecordingError::ThreadPoison)? + .to_vec(), + spec, + )) } pub fn samples_to_wav( samples: impl IntoIterator, spec: &WavSpec, - filepath: impl AsRef + filepath: impl AsRef, ) -> Result, hound::Error> { let wav_bytes = std::io::Cursor::new(Vec::::new()); - trace!("creating a wav file writer to {}", filepath.as_ref().display()); + trace!( + "creating a wav file writer to {}", + filepath.as_ref().display() + ); let mut file_writer = hound::WavWriter::create(&filepath, *spec)?; trace!("writing samples..."); @@ -193,4 +208,4 @@ pub fn samples_to_wav( file_writer.finalize()?; Ok(wav_bytes.into_inner()) -} \ No newline at end of file +} diff --git a/own_mcp/src/lib.rs b/own_mcp/src/lib.rs index 8d92737..f2f7c00 100644 --- a/own_mcp/src/lib.rs +++ b/own_mcp/src/lib.rs @@ -1,4 +1,4 @@ -pub mod mcp; pub mod audio; +pub mod mcp; -pub use mcp::chat::AgentChat; \ No newline at end of file +pub use mcp::chat::AgentChat; diff --git a/own_mcp/src/mcp/chat.rs b/own_mcp/src/mcp/chat.rs index 735dbc3..dee3345 100644 --- a/own_mcp/src/mcp/chat.rs +++ b/own_mcp/src/mcp/chat.rs @@ -1,13 +1,13 @@ use crate::mcp::MCPClient; -use ollama_rs::error::OllamaError; -use ollama_rs::generation::chat::request::ChatMessageRequest; -use ollama_rs::generation::chat::ChatMessage; -use ollama_rs::generation::tools::ToolInfo; use ollama_rs::Ollama; -use rmcp::model::{CallToolRequestParams, CallToolResult}; +use ollama_rs::error::OllamaError; +use ollama_rs::generation::chat::ChatMessage; +use ollama_rs::generation::chat::request::ChatMessageRequest; +use ollama_rs::generation::tools::ToolInfo; use rmcp::ServiceError; -use std::collections::HashMap; +use rmcp::model::{CallToolRequestParams, CallToolResult}; use serde::Deserialize; +use std::collections::HashMap; use thiserror::Error; #[derive(Debug, Copy, Clone, Default, Deserialize, PartialEq)] @@ -164,21 +164,29 @@ impl AgentChat { /// * `name`: must be in the format "mcp_server_name:tool_or_resource_name". /// /// returns: Option<&RestrictedTool> - pub fn get_tool( - mpc_server_data: &MCPServerData, - name: String, - ) -> Option<&RestrictedTool> { + pub fn get_tool(mpc_server_data: &MCPServerData, name: String) -> Option<&RestrictedTool> { mpc_server_data .translated_tools .iter() .find(|tool| tool.tool_info.function.name == name) } - pub fn set_permission(&mut self, full_tool_name: String, permission: ToolPermission) -> Result<(), ChatError> { + pub fn set_permission( + &mut self, + full_tool_name: String, + permission: ToolPermission, + ) -> Result<(), ChatError> { let (mcp_server_name, tool_name) = Self::parse_tool_name(full_tool_name.clone())?; - let mcp_server_data: &mut MCPServerData = self.mcp_servers.get_mut(&mcp_server_name.clone()).ok_or(ChatError::ServiceNotFoundError(mcp_server_name.clone()))?; - let tool_index = mcp_server_data.translated_tools.iter().position(|tool| tool.tool_info.function.name == full_tool_name).ok_or(ChatError::ToolNotFoundError(tool_name))?; + let mcp_server_data: &mut MCPServerData = self + .mcp_servers + .get_mut(&mcp_server_name.clone()) + .ok_or(ChatError::ServiceNotFoundError(mcp_server_name.clone()))?; + let tool_index = mcp_server_data + .translated_tools + .iter() + .position(|tool| tool.tool_info.function.name == full_tool_name) + .ok_or(ChatError::ToolNotFoundError(tool_name))?; mcp_server_data.translated_tools[tool_index].permission = permission; Ok(()) } @@ -187,12 +195,12 @@ impl AgentChat { &mut self, user_message: String, mut permission_request_callback: impl FnMut(String, String) -> C, - ) -> Result - { + ) -> Result { let all_tools: Vec = self .get_all_tools() - .filter_map(|restricted_tool|{ - if restricted_tool.permission == ToolPermission::Denied { // filters denied tools to save tokens + .filter_map(|restricted_tool| { + if restricted_tool.permission == ToolPermission::Denied { + // filters denied tools to save tokens None } else { Some(restricted_tool.tool_info.clone()) @@ -219,16 +227,22 @@ impl AgentChat { for tool_call in &response.message.tool_calls { log::debug!("calling tool {}", tool_call.function.name); - let result = self.call_tool(tool_call, &mut permission_request_callback).await?; + let result = self + .call_tool(tool_call, &mut permission_request_callback) + .await?; // serialize structured content if it exists - if let Some(structured_content) = result.clone().and_then(|result|result.structured_content) { + if let Some(structured_content) = + result.clone().and_then(|result| result.structured_content) + { log::debug!("structured content: {structured_content:#?}"); - self.message_history.push(ChatMessage::tool(structured_content.to_string())); + self.message_history + .push(ChatMessage::tool(structured_content.to_string())); } else { let contents = match result { - Some(result) => result.content + Some(result) => result + .content .iter() .filter_map(|content| content.as_text()) .map(|text_content| text_content.text.clone()) @@ -270,7 +284,7 @@ impl AgentChat { &self, tool_call: &ollama_rs::generation::tools::ToolCall, permission_request_callback: &mut impl FnMut(String, String) -> C, - ) -> Result, ChatError> { + ) -> Result, ChatError> { let arguments_json_object = tool_call .function .arguments @@ -286,10 +300,10 @@ impl AgentChat { let mcp_server_data = self .get_mcp_server_by_name(mcp_server_name.clone()) .ok_or(ChatError::ServiceNotFoundError(mcp_server_name.clone()))?; - let restricted_tool = AgentChat::get_tool(&mcp_server_data, full_unparsed_tool_name.clone()) - .ok_or(ChatError::ToolNotFoundError( - full_unparsed_tool_name.clone(), - ))?; + let restricted_tool = + AgentChat::get_tool(&mcp_server_data, full_unparsed_tool_name.clone()).ok_or( + ChatError::ToolNotFoundError(full_unparsed_tool_name.clone()), + )?; match restricted_tool.permission { ToolPermission::Allowed => { @@ -301,7 +315,8 @@ impl AgentChat { } ToolPermission::Ask => { log::trace!("ask restricted tool: {:?}", restricted_tool); - let response = permission_request_callback(mcp_server_name, tool_name.clone()).await; + let response = + permission_request_callback(mcp_server_name, tool_name.clone()).await; match response { PermissionAnswer::Granted => { log::trace!("granted restricted tool: {:?}", restricted_tool); @@ -323,7 +338,9 @@ impl AgentChat { request_params ); - Ok(Some(mcp_server_data.client.call_tool(request_params).await?)) + Ok(Some( + mcp_server_data.client.call_tool(request_params).await?, + )) } } @@ -356,16 +373,21 @@ mod tests { MCP_TEST_SERVER_URL, None::, Implementation::new("test", env!("CARGO_PKG_VERSION")), - ).await.unwrap(), + ) + .await + .unwrap(), )]); - let mut chat = AgentChat::new(ollama, "lfm2.5-thinking:1.2b".to_string(), mcp_clients).await.unwrap(); + let mut chat = AgentChat::new(ollama, "lfm2.5-thinking:1.2b".to_string(), mcp_clients) + .await + .unwrap(); let tools: Vec = chat.get_all_tools().cloned().collect(); assert_eq!(tools.len(), 1); assert_eq!(tools[0].permission, ToolPermission::Ask); - chat.set_permission("test:echo".to_string(), ToolPermission::Allowed).unwrap(); + chat.set_permission("test:echo".to_string(), ToolPermission::Allowed) + .unwrap(); let tools: Vec = chat.get_all_tools().cloned().collect(); assert_eq!(tools[0].permission, ToolPermission::Allowed); diff --git a/own_mcp/src/mcp/mod.rs b/own_mcp/src/mcp/mod.rs index ba657a2..58e8adc 100644 --- a/own_mcp/src/mcp/mod.rs +++ b/own_mcp/src/mcp/mod.rs @@ -34,24 +34,29 @@ pub async fn get_client( } fn generate_random_mcp_server_name() -> String { - use rand::distr::Alphanumeric; use rand::RngExt; + use rand::distr::Alphanumeric; let mut rng = rand::rng(); - (0..5) - .map(|_| rng.sample(Alphanumeric) as char) - .collect() + (0..5).map(|_| rng.sample(Alphanumeric) as char).collect() } -pub fn guaranteed_mcp_server_name(user_specified_name: Option, client: &MCPClient) -> String { +pub fn guaranteed_mcp_server_name( + user_specified_name: Option, + client: &MCPClient, +) -> String { if let Some(user_specified_name) = user_specified_name { user_specified_name } else if let Some(peer_info) = client.peer_info() { peer_info.server_info.name.clone() } else { let random_name = generate_random_mcp_server_name(); - log::warn!("no name was specified by the user or the client, so a random name had to be generated: `{}` for {:#?}", random_name, client); + log::warn!( + "no name was specified by the user or the client, so a random name had to be generated: `{}` for {:#?}", + random_name, + client + ); random_name } -} \ No newline at end of file +} diff --git a/own_mcp/src/mcp/translation.rs b/own_mcp/src/mcp/translation.rs index 4f0f4a1..a9d2b57 100644 --- a/own_mcp/src/mcp/translation.rs +++ b/own_mcp/src/mcp/translation.rs @@ -74,10 +74,13 @@ fn tool_info_from_mcp_resource( description: format!( "type: {} - {}", mcp_resource - .mime_type.clone() + .mime_type + .clone() .unwrap_or("No type provided".to_string()), - mcp_resource.clone() - .description.clone() + mcp_resource + .clone() + .description + .clone() .unwrap_or("No description".to_string()) ), parameters: schema, diff --git a/src/config.rs b/src/config.rs index 4bbcc63..614f53e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,15 +1,15 @@ -use own_assist_common::config_loader::ConfigLoadingError; -use std::collections::HashMap; -use ollama_rs::headers::{HeaderMap, HeaderValue}; use ollama_rs::Ollama; +use ollama_rs::headers::{HeaderMap, HeaderValue}; +use own_assist_common::config_from_file; +use own_assist_common::config_loader::ConfigLoadingError; use own_mcp::audio::{AudioClient, AudioClientTrait}; +use own_mcp::mcp::chat::{ChatError, ToolPermission}; +use own_mcp::mcp::{MCPClient, guaranteed_mcp_server_name}; +use own_mcp::{AgentChat, mcp}; use rmcp::model::Implementation; use serde::Deserialize; +use std::collections::HashMap; use url::Url; -use own_mcp::{mcp, AgentChat}; -use own_mcp::mcp::{guaranteed_mcp_server_name, MCPClient}; -use own_mcp::mcp::chat::{ChatError, ToolPermission}; -use own_assist_common::config_from_file; #[derive(Debug, Deserialize)] pub struct Config { @@ -26,7 +26,7 @@ impl Config { pub fn from_file() -> Result { config_from_file("assist.toml") } - + pub fn ollama_config(&self) -> &OllamaConfig { &self.ollama } @@ -35,7 +35,10 @@ impl Config { let mut ollama_headers = HeaderMap::new(); if let Some(authorization_header) = &self.ollama.authorization { - ollama_headers.append("Authorization", HeaderValue::from_str(authorization_header).unwrap()); + ollama_headers.append( + "Authorization", + HeaderValue::from_str(authorization_header).unwrap(), + ); } let ollama_default_url = Url::parse("http://127.0.0.1:11434").unwrap(); @@ -58,7 +61,9 @@ impl Config { } } - pub async fn mcp_clients(&self) -> Result, rmcp::service::ClientInitializeError> { + pub async fn mcp_clients( + &self, + ) -> Result, rmcp::service::ClientInitializeError> { let mut mcp_clients: HashMap = HashMap::new(); for mcp_server in &self.mcp_servers { @@ -66,7 +71,8 @@ impl Config { mcp_server.url.as_str(), mcp_server.authorization.clone(), Implementation::new("own_assist", env!("CARGO_PKG_VERSION")), - ).await?; + ) + .await?; let server_name = guaranteed_mcp_server_name(mcp_server.name.clone(), &client); let server_data = client; @@ -78,13 +84,21 @@ impl Config { } pub async fn set_tool_permissions(&self, agent_chat: &mut AgentChat) -> Result<(), ChatError> { - if let Some(permissions) = &self.permissions && !permissions.is_empty() { + if let Some(permissions) = &self.permissions + && !permissions.is_empty() + { for permission_config in permissions { - log::info!("set permission for `{}` to {:?}", permission_config.tool_name, permission_config.permission); - agent_chat.set_permission(permission_config.tool_name.clone(), permission_config.permission)?; + log::info!( + "set permission for `{}` to {:?}", + permission_config.tool_name, + permission_config.permission + ); + agent_chat.set_permission( + permission_config.tool_name.clone(), + permission_config.permission, + )?; } - } - else { + } else { log::debug!("no permissions were provided.") } @@ -138,5 +152,5 @@ pub struct MCPServerConfig { pub struct PermissionConfig { #[serde(rename = "tool-name")] pub tool_name: String, - pub permission: ToolPermission -} \ No newline at end of file + pub permission: ToolPermission, +} diff --git a/src/human_interface/audio.rs b/src/human_interface/audio.rs index 3eae9ea..14d3a6a 100644 --- a/src/human_interface/audio.rs +++ b/src/human_interface/audio.rs @@ -1,5 +1,6 @@ -use crate::translate; use crate::human_interface::{HumanInterface, HumanInterfaceError}; +use crate::tlt; +use crate::translate; use cpal::traits::DeviceTrait; use own_mcp::audio::models::VoiceRequest; use own_mcp::audio::{AudioClient, AudioClientTrait}; @@ -8,7 +9,6 @@ use std::io; use std::path::Path; use tokio::fs::create_dir_all; use tokio::io::AsyncBufReadExt; -use crate::tlt; const TEMPORARY_AUDIO_PATH: &str = "temporary_audio"; @@ -31,7 +31,7 @@ impl Audio { async fn wait_for_input_line(expected_line: &str) -> Result<(), io::Error> { loop { let stdin_reader = tokio::io::BufReader::new(tokio::io::stdin()); - log::trace!("wating for '{expected_line}' to continue"); + log::trace!("waiting for '{expected_line}' to continue"); if let Some(line) = stdin_reader.lines().next_line().await? { log::debug!("Received line: {line:?}"); if line == expected_line { @@ -53,8 +53,8 @@ impl Audio { log::debug!("Received line: {line:?}"); match line.as_str() { - GRANTED_LINE => { return Ok(PermissionAnswer::Granted) }, - DENIED_LINE => { return Ok(PermissionAnswer::Denied) }, + GRANTED_LINE => return Ok(PermissionAnswer::Granted), + DENIED_LINE => return Ok(PermissionAnswer::Denied), _ => {} }; } @@ -64,10 +64,7 @@ impl Audio { async fn play_text(&self, text: String) -> Result<(), HumanInterfaceError> { let bytes = self .client - .tts(VoiceRequest { - text, - config: None, - }) + .tts(VoiceRequest { text, config: None }) .await .map_err(|e| HumanInterfaceError::Other(format!("Failed getting tts: {e}")))?; @@ -75,9 +72,11 @@ impl Audio { tokio::fs::write(&wav_file_path, &bytes).await?; - let handle = rodio::DeviceSinkBuilder::open_default_sink().expect("open default audio stream"); + let handle = + rodio::DeviceSinkBuilder::open_default_sink().expect("open default audio stream"); let file = std::fs::File::open(wav_file_path)?; - let player = rodio::play(handle.mixer(), file).map_err(|e| HumanInterfaceError::Other(format!("Failed playing audio: {e}")))?; + let player = rodio::play(handle.mixer(), file) + .map_err(|e| HumanInterfaceError::Other(format!("Failed playing audio: {e}")))?; player.sleep_until_end(); @@ -111,14 +110,14 @@ impl HumanInterface for Audio { let wav_file_path = Path::new(TEMPORARY_AUDIO_PATH).join("recording.wav"); - samples_to_wav( - samples, - &wav_spec, - &wav_file_path, - ) - .map_err(|e| HumanInterfaceError::Other(format!("hound error: {e}")))?; + samples_to_wav(samples, &wav_spec, &wav_file_path) + .map_err(|e| HumanInterfaceError::Other(format!("hound error: {e}")))?; - let transcription = self.client.transcribe(wav_file_path).await.map_err(|e|HumanInterfaceError::Other(format!("failed to transcribe: {e}")))?; + let transcription = self + .client + .transcribe(wav_file_path) + .await + .map_err(|e| HumanInterfaceError::Other(format!("failed to transcribe: {e}")))?; log::info!("transcription: {transcription:?}"); @@ -130,7 +129,12 @@ impl HumanInterface for Audio { mcp_server_name: String, tool_name: String, ) -> Result { - self.play_text(tlt!("tool_allow_question_audio", mcp_server_name, tool_name)).await?; + self.play_text(tlt!( + "tool_allow_question_audio", + mcp_server_name, + tool_name + )) + .await?; let decision = Self::wait_for_decision().await?; log::debug!("decision: {decision:?}"); @@ -146,11 +150,13 @@ mod tests { const AUTH_HEADER: &str = include_str!(".AUTH_HEADER"); async fn get_test_human_interface() -> Audio { - Audio::new(AudioClient::new( - url::Url::parse("https://audio.mboemer.de" - ).unwrap()). - with_authorization( - AUTH_HEADER.to_string()), cpal::Host::default().default_input_device().unwrap()).await.unwrap() + Audio::new( + AudioClient::new(url::Url::parse("https://audio.mboemer.de").unwrap()) + .with_authorization(AUTH_HEADER.to_string()), + cpal::Host::default().default_input_device().unwrap(), + ) + .await + .unwrap() } #[tokio::test] @@ -166,6 +172,9 @@ mod tests { #[tokio::test] async fn test_tts_playing() { let human_interface = get_test_human_interface().await; - human_interface.play_text(String::from("Peter ist jetzt in deinem PC.")).await.unwrap(); + human_interface + .play_text(String::from("Peter ist jetzt in deinem PC.")) + .await + .unwrap(); } } diff --git a/src/human_interface/cli.rs b/src/human_interface/cli.rs index 3ce300c..61438e3 100644 --- a/src/human_interface/cli.rs +++ b/src/human_interface/cli.rs @@ -1,11 +1,11 @@ -use std::time::Duration; -use console::style; -use crate::translate; use crate::human_interface::{HumanInterface, HumanInterfaceError}; +use crate::tlt; +use crate::translate; +use console::style; use dialoguer::theme::ColorfulTheme; use dialoguer::{Confirm, Input}; use own_mcp::mcp::chat::PermissionAnswer; -use crate::tlt; +use std::time::Duration; pub struct CommandLine { progress_bar: Option, @@ -29,7 +29,7 @@ impl HumanInterface for CommandLine { self.progress_bar = None; } - println!("{}: {}",style( tlt!("assistant")).bold().italic(),message); + println!("{}: {}", style(tlt!("assistant")).bold().italic(), message); Ok(()) } @@ -41,14 +41,18 @@ impl HumanInterface for CommandLine { // waiting for agent response self.progress_bar = Some(Self::spinner()); - self.progress_bar.clone().unwrap().enable_steady_tick(Duration::from_millis(100)); + self.progress_bar + .clone() + .unwrap() + .enable_steady_tick(Duration::from_millis(100)); Ok(user_message) } - async fn ask_for_permission(&self, - mcp_server_name: String, - tool_name: String, + async fn ask_for_permission( + &self, + mcp_server_name: String, + tool_name: String, ) -> Result { if let Some(progress_bar) = &self.progress_bar { progress_bar.finish_and_clear(); @@ -65,4 +69,3 @@ impl HumanInterface for CommandLine { } } } - diff --git a/src/human_interface/mod.rs b/src/human_interface/mod.rs index aedd57f..116f81f 100644 --- a/src/human_interface/mod.rs +++ b/src/human_interface/mod.rs @@ -1,21 +1,28 @@ -pub mod cli; pub mod audio; +pub mod cli; -use thiserror::Error; use own_mcp::mcp::chat::PermissionAnswer; +use thiserror::Error; #[derive(Debug, Error)] pub enum HumanInterfaceError { #[error(transparent)] IoError(#[from] std::io::Error), #[error("other error in human interface: {0}")] - Other(String) + Other(String), } pub trait HumanInterface { - fn agent_message(&mut self, message: String) -> impl Future>; + fn agent_message( + &mut self, + message: String, + ) -> impl Future>; fn expect_user_message(&mut self) -> impl Future>; - fn ask_for_permission(&self, mcp_server_name: String, tool_name:String) -> impl Future>; -} \ No newline at end of file + fn ask_for_permission( + &self, + mcp_server_name: String, + tool_name: String, + ) -> impl Future>; +} diff --git a/src/i18n/mod.rs b/src/i18n/mod.rs index 08ef250..8453e98 100644 --- a/src/i18n/mod.rs +++ b/src/i18n/mod.rs @@ -77,11 +77,10 @@ mod tests { let formatted = format_dynamically("{...} is cool!".to_string(), vec!["Rust".to_string()]); assert_eq!(formatted, "Rust is cool!"); - let formatted = format_dynamically("{...}, {...} and {...} are three consecutive numbers.".to_string(), vec![ - "1".to_string(), - "2".to_string(), - "3".to_string(), - ]); + let formatted = format_dynamically( + "{...}, {...} and {...} are three consecutive numbers.".to_string(), + vec!["1".to_string(), "2".to_string(), "3".to_string()], + ); assert_eq!(formatted, "1, 2 and 3 are three consecutive numbers."); } } diff --git a/src/main.rs b/src/main.rs index 10018b2..810f16f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,9 +10,8 @@ use crate::model::create_model_from_config; use base64::Engine; use clap::Parser; use cpal::traits::HostTrait; -use own_mcp::AgentChat; -use std::fmt::Display; use own_assist_common::exit_msg; +use own_mcp::AgentChat; /// partial mcp client with cli and voice interaction #[derive(Parser, Debug)] @@ -76,10 +75,7 @@ async fn main() { env_logger::init(); let config = Config::from_file() - .inspect_err(|e| { - log::error!("error loading assist.toml: {}", e); - std::process::exit(1); - }) + .inspect_err(exit_msg!("error loading assist.toml: {}")) .unwrap(); let ollama = config.ollama_instance(); @@ -98,7 +94,7 @@ async fn main() { .inspect_err(exit_msg!("failed creating MCP clients")) .unwrap(); - let mut agent_chat = own_mcp::AgentChat::new(ollama, model_name.clone(), mcp_clients) + let mut agent_chat = AgentChat::new(ollama, model_name.clone(), mcp_clients) .await .inspect_err(exit_msg!("failed to create agent")) .unwrap(); @@ -135,4 +131,4 @@ async fn main() { }; } }; -} \ No newline at end of file +} diff --git a/src/model.rs b/src/model.rs index 163aa54..a4b1ff4 100644 --- a/src/model.rs +++ b/src/model.rs @@ -9,19 +9,16 @@ use ollama_rs::models::create::{CreateModelRequest, CreateModelStatus}; pub async fn create_model_from_config( ollama: &Ollama, config: &OllamaModelConfig, - interface: &HumanInterface + interface: &HumanInterface, ) -> Result { let model_options = ModelOptions::default() .num_ctx(config.context_size.unwrap_or(2048)) // 2048 is the ollama default. .temperature(config.temperature.unwrap_or(0.8)); // 0.8 is the ollama default - let system_prompt = config - .system_prompt - .clone() - .unwrap_or(match interface { - HumanInterface::Cli => tlt!("system_prompt_cli"), - HumanInterface::Audio => tlt!("system_prompt_audio"), - }); + let system_prompt = config.system_prompt.clone().unwrap_or(match interface { + HumanInterface::Cli => tlt!("system_prompt_cli"), + HumanInterface::Audio => tlt!("system_prompt_audio"), + }); // print message to warn user of long waiting times if let Ok(models) = ollama.list_local_models().await