diff --git a/src/output.rs b/src/output.rs index 2513dad..4fe0a09 100644 --- a/src/output.rs +++ b/src/output.rs @@ -124,7 +124,12 @@ impl Output for TelegramOutput { // send tool call log as .md file if any if !self.tool_log.is_empty() { let md = self.tool_log.join("\n"); - let tmp = format!("/tmp/noc_tools_{}.md", std::process::id()); + // extract tool names for filename + let names: Vec<&str> = self.tool_log.iter() + .filter_map(|s| s.strip_prefix('[')?.split('(').next()) + .collect(); + let label = if names.is_empty() { "tools".to_string() } else { names.join("_") }; + let tmp = format!("/tmp/{label}.md"); if std::fs::write(&tmp, &md).is_ok() { let input_file = InputFile::file(std::path::Path::new(&tmp)); let _ = self.bot.send_document(self.chat_id, input_file).await; @@ -141,12 +146,27 @@ impl Output for TelegramOutput { } async fn send_file(&self, path: &Path, caption: &str) -> Result { + let ext = path.extension().and_then(|e| e.to_str()).unwrap_or(""); let input_file = InputFile::file(path); - let mut req = self.bot.send_document(self.chat_id, input_file); - if !caption.is_empty() { - req = req.caption(caption); + match ext { + "ogg" | "oga" => { + self.bot.send_voice(self.chat_id, input_file).await?; + } + "wav" | "mp3" | "m4a" | "flac" => { + let mut req = self.bot.send_audio(self.chat_id, input_file); + if !caption.is_empty() { + req = req.caption(caption); + } + req.await?; + } + _ => { + let mut req = self.bot.send_document(self.chat_id, input_file); + if !caption.is_empty() { + req = req.caption(caption); + } + req.await?; + } } - req.await?; Ok(true) } } diff --git a/src/stream.rs b/src/stream.rs index 37543cf..ca54ead 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -193,14 +193,20 @@ pub async fn run_openai_with_tools( for tc in &tool_calls { info!(tool = %tc.name, "executing tool call"); + let args_preview = truncate_at_char_boundary(&tc.arguments, 200); let _ = output - .status(&format!("[{}({})]", tc.name, truncate_at_char_boundary(&tc.arguments, 100))) + .status(&format!("### `{}`\n```json\n{args_preview}\n```", tc.name)) .await; let result = execute_tool(&tc.name, &tc.arguments, state, output, sid, config, chat_id) .await; + let result_preview = truncate_at_char_boundary(&result, 500); + let _ = output + .status(&format!("**Result** ({} bytes)\n```\n{result_preview}\n```\n---", result.len())) + .await; + messages.push(serde_json::json!({ "role": "tool", "tool_call_id": tc.id, diff --git a/src/tools.rs b/src/tools.rs index cf9dd16..7b5303a 100644 --- a/src/tools.rs +++ b/src/tools.rs @@ -583,6 +583,7 @@ pub async fn execute_tool( } "gen_voice" => { let text = args["text"].as_str().unwrap_or(""); + info!("gen_voice text={:?} args={}", text, truncate_at_char_boundary(arguments, 200)); if text.is_empty() { return "Error: text is required".to_string(); } @@ -611,9 +612,13 @@ pub async fn execute_tool( Ok(Ok(out)) => { let stderr = String::from_utf8_lossy(&out.stderr); let stdout = String::from_utf8_lossy(&out.stdout); + warn!("gen_voice failed (exit={}): stdout={stdout} stderr={stderr}", out.status.code().unwrap_or(-1)); format!("gen_voice failed: {stdout} {stderr}") } - Ok(Err(e)) => format!("gen_voice exec error: {e}"), + Ok(Err(e)) => { + warn!("gen_voice exec error: {e}"); + format!("gen_voice exec error: {e}") + } Err(_) => "gen_voice timeout (120s)".to_string(), } }