Conversation
…nds, and auto-registration Add 7 new ChannelContent variants (DeleteMessage, Audio, Animation, Sticker, MediaGroup, Poll, PollAnswer) with full send/receive support in the Telegram adapter. New API methods: sendAudio, sendAnimation, sendSticker, sendMediaGroup, sendPoll, deleteMessage, setMyCommands, deleteMyCommands, getMyCommands. Inbound parsing now handles audio files (as Audio, not Voice), animations (checked before video to avoid misclassification), stickers, and poll answers. Bot command menu is auto-registered on start() using built-in defaults (BUILTIN_COMMANDS) when no custom commands are configured, and re-registered on /start or first incoming message via the polling loop. Bridge updated with exhaustive match arms in content_to_text and dispatch_message for all new variants, plus 8 new unit tests.
Agents using Messaging or Automation profiles had no way to send images, files, or other media to channel users because channel_send was only available in the Full profile. This made the agent suggest using the API directly instead of its built-in tools.
The channel awareness section in the system prompt told agents about formatting and character limits but never mentioned channel_send. Agents had the tool available but did not know to use it for sending images or files, proposing raw API calls instead. Add a channel_send hint to the Channel section when the tool is granted, including the recipient ID so the agent can use it immediately. Also add a tool_hint entry so channel_send appears with a description in the Your Tools section.
Add send_channel_poll to KernelHandle trait and kernel implementation. Extend channel_send tool with poll_question, poll_options, poll_is_quiz, poll_correct_option, poll_explanation parameters. Telegram adapter stores poll context (question + options) keyed by poll_id and attaches it to inbound PollAnswer messages so agents see readable answers instead of bare numeric indices.
…ivity api_send_media_group and api_send_poll previously logged non-429 errors and returned Ok, making callers believe delivery succeeded. Both now return Err with status and body. channel_send's poll branch now rejects requests that also carry image_url/image_path/file_url/file_path, matching the schema's documented mutual exclusivity.
Intercept /agents in both Command and Text dispatch paths so Telegram bot-command entities reach the interactive handler. Emits an InteractiveMessage with one button per agent, truncating names to fit the 64-byte callback_data limit.
Replace plain-text /models with a multi-level Telegram inline keyboard: providers list → provider's models (with Back) → confirmation with cleared keyboard. Adds ChannelContent::EditInteractive for in-place editMessageText updates and filters the provider menu to only those with is_available() auth status.
There was a problem hiding this comment.
Sorry @leszek3737, you have reached your weekly rate limit of 2500000 diff characters.
Please try again later or upgrade to continue using Sourcery
There was a problem hiding this comment.
Code Review
This pull request introduces comprehensive support for rich media and interactive features across the channel bridge and Telegram adapter, including audio, animations, stickers, media groups, and polls. It also implements bot command registration and interactive menus for agent and model selection. Feedback focuses on high-severity risks related to unsafe string slicing at byte boundaries, a potential memory leak in poll context management, and the need for improved validation for poll options and media group captions.
| let safe_name = if agent_name.len() > max_name_bytes { | ||
| &agent_name[..max_name_bytes] | ||
| } else { | ||
| &agent_name | ||
| }; |
There was a problem hiding this comment.
Slicing a string by a byte index without verifying UTF-8 character boundaries can cause a panic if the index falls in the middle of a multi-byte character. Since agent_name can contain arbitrary Unicode characters, this is a high-risk operation. This pattern occurs in several other places in this file (lines 1898, 2002, 2044, 2287, 2328) and should be fixed globally.
| let safe_name = if agent_name.len() > max_name_bytes { | |
| &agent_name[..max_name_bytes] | |
| } else { | |
| &agent_name | |
| }; | |
| let safe_name = if agent_name.len() > max_name_bytes { | |
| let mut end = max_name_bytes; | |
| while end > 0 && !agent_name.is_char_boundary(end) { | |
| end -= 1; | |
| } | |
| &agent_name[..end] | |
| } else { | |
| &agent_name | |
| }; |
| let action = if btn.action.len() > 64 { | ||
| btn.action[..64].to_string() | ||
| } else { | ||
| btn.action.clone() | ||
| }; |
There was a problem hiding this comment.
Similar to the issue in bridge.rs, slicing btn.action at a fixed byte length (64) without checking UTF-8 boundaries will cause a panic if the slice occurs inside a multi-byte character.
let action = if btn.action.len() > 64 {
let mut end = 64;
while end > 0 && !btn.action.is_char_boundary(end) {
end -= 1;
}
btn.action[..end].to_string()
} else {
btn.action.clone()
};| clear_done_reaction: bool, | ||
| /// Bot commands registered in the Telegram command menu. | ||
| commands: Vec<BotCommand>, | ||
| poll_contexts: Arc<DashMap<String, PollContext>>, |
There was a problem hiding this comment.
| if i == 0 { | ||
| if let Some(cap) = caption { | ||
| v["caption"] = serde_json::Value::String(cap.clone()); | ||
| v["parse_mode"] = | ||
| serde_json::Value::String(PARSE_MODE_HTML.to_string()); | ||
| } | ||
| } |
There was a problem hiding this comment.
The current implementation restricts captions to only the first item in a media group. While Telegram often displays the first caption as the primary description for the album, it is technically valid to have captions on individual items. More importantly, if a caller provides captions for subsequent items, they are silently dropped here. This logic should be applied to videos as well (lines 707-713).
if let Some(cap) = caption {
v["caption"] = serde_json::Value::String(cap.clone());
v["parse_mode"] =
serde_json::Value::String(PARSE_MODE_HTML.to_string());
}| if poll_options.len() < 2 { | ||
| return Err("poll_options must have at least 2 options".to_string()); | ||
| } | ||
|
|
||
| let is_quiz = input | ||
| .get("poll_is_quiz") | ||
| .and_then(|v| v.as_bool()) | ||
| .unwrap_or(false); | ||
| let correct_option_id = input | ||
| .get("poll_correct_option") | ||
| .and_then(|v| v.as_u64()) | ||
| .map(|n| n as u8); |
There was a problem hiding this comment.
Telegram polls are limited to a maximum of 10 options. Additionally, for quiz mode (is_quiz: true), the correct_option_id is mandatory and must be a valid index within the poll_options array. Adding these validations here ensures the agent receives a clear error message before the request fails at the API level.
if poll_options.len() < 2 || poll_options.len() > 10 {
return Err("poll_options must have between 2 and 10 options".to_string());
}
let is_quiz = input
.get("poll_is_quiz")
.and_then(|v| v.as_bool())
.unwrap_or(false);
let correct_option_id = input
.get("poll_correct_option")
.and_then(|v| v.as_u64())
.map(|n| n as u8);
if is_quiz {
let Some(id) = correct_option_id else {
return Err("poll_correct_option is required for quiz mode".to_string());
};
if id as usize >= poll_options.len() {
return Err(format!(
"poll_correct_option index {id} is out of bounds (0-{})".to_string(),
poll_options.len() - 1
));
}
}
Type
Summary
Changes
Attribution
Co-authored-by, commit preservation, or explicit credit in the PR body)Testing
cargo clippy --workspace --all-targets -- -D warningspassescargo test --workspacepassesSecurity