diff --git a/src/agent/loop_.rs b/src/agent/loop_.rs index bce5ce0..e21e760 100644 --- a/src/agent/loop_.rs +++ b/src/agent/loop_.rs @@ -15,6 +15,9 @@ use std::sync::{Arc, LazyLock}; use std::time::Instant; use uuid::Uuid; +/// Minimum characters per chunk when relaying LLM text to a streaming draft. +const STREAM_CHUNK_MIN_CHARS: usize = 80; + /// Default maximum agentic tool-use iterations per user message to prevent runaway loops. /// Used as a safe fallback when `max_tool_iterations` is unset or configured as zero. const DEFAULT_MAX_TOOL_ITERATIONS: usize = 10; @@ -944,11 +947,12 @@ pub(crate) async fn run_tool_call_loop( // If a streaming sender is provided, relay the text in small chunks // so the channel can progressively update the draft message. if let Some(ref tx) = on_delta { - // Split on whitespace boundaries, accumulating ~80-char chunks. + // Split on whitespace boundaries, accumulating chunks of at least + // STREAM_CHUNK_MIN_CHARS characters for progressive draft updates. let mut chunk = String::new(); for word in display_text.split_inclusive(char::is_whitespace) { chunk.push_str(word); - if chunk.len() >= 80 { + if chunk.len() >= STREAM_CHUNK_MIN_CHARS { if tx.send(std::mem::take(&mut chunk)).await.is_err() { break; // receiver dropped } diff --git a/src/channels/telegram.rs b/src/channels/telegram.rs index 6e67b3b..82430a8 100644 --- a/src/channels/telegram.rs +++ b/src/channels/telegram.rs @@ -1264,11 +1264,12 @@ impl Channel for TelegramChannel { message_id: &str, text: &str, ) -> anyhow::Result<()> { + let (chat_id, _) = Self::parse_reply_target(recipient); + // Rate-limit edits per chat { - let (chat_id_for_limit, _) = Self::parse_reply_target(recipient); let last_edits = self.last_draft_edit.lock(); - if let Some(last_time) = last_edits.get(&chat_id_for_limit) { + if let Some(last_time) = last_edits.get(&chat_id) { let elapsed = u64::try_from(last_time.elapsed().as_millis()).unwrap_or(u64::MAX); if elapsed < self.draft_update_interval_ms { return Ok(()); @@ -1276,8 +1277,6 @@ impl Channel for TelegramChannel { } } - let (chat_id, _) = Self::parse_reply_target(recipient); - // Truncate to Telegram limit for mid-stream edits (UTF-8 safe) let display_text = if text.len() > TELEGRAM_MAX_MESSAGE_LENGTH { let mut end = 0; @@ -1333,8 +1332,12 @@ impl Channel for TelegramChannel { message_id: &str, text: &str, ) -> anyhow::Result<()> { + let text = &strip_tool_call_tags(text); let (chat_id, thread_id) = Self::parse_reply_target(recipient); + // Clean up rate-limit tracking for this chat + self.last_draft_edit.lock().remove(&chat_id); + // If text exceeds limit, delete draft and send as chunked messages if text.len() > TELEGRAM_MAX_MESSAGE_LENGTH { let msg_id = match message_id.parse::() { @@ -1375,7 +1378,7 @@ impl Channel for TelegramChannel { }; // Try editing with Markdown formatting - let mut body = serde_json::json!({ + let body = serde_json::json!({ "chat_id": chat_id, "message_id": msg_id, "text": text, @@ -1394,12 +1397,16 @@ impl Channel for TelegramChannel { } // Markdown failed — retry without parse_mode - body.as_object_mut().unwrap().remove("parse_mode"); + let plain_body = serde_json::json!({ + "chat_id": chat_id, + "message_id": msg_id, + "text": text, + }); let resp = self .client .post(self.api_url("editMessageText")) - .json(&body) + .json(&plain_body) .send() .await?;