Skip to content

Feat/telegram#32

Closed
leszek3737 wants to merge 9 commits intomainfrom
feat/telegram
Closed

Feat/telegram#32
leszek3737 wants to merge 9 commits intomainfrom
feat/telegram

Conversation

@leszek3737
Copy link
Copy Markdown
Owner

Type

  • Agent template (TOML)
  • Skill (Python/JS/Prompt)
  • Channel adapter
  • LLM provider
  • Built-in tool
  • Bug fix
  • Feature (Rust)
  • Documentation / Translation
  • Refactor / Performance
  • CI / Tooling
  • Other

Summary

Changes

Attribution

  • This PR preserves author attribution for any adapted prior work (Co-authored-by, commit preservation, or explicit credit in the PR body)

Testing

  • cargo clippy --workspace --all-targets -- -D warnings passes
  • cargo test --workspace passes
  • Live integration tested (if applicable)

Security

  • No new unsafe code
  • No secrets or API keys in diff
  • User input validated at boundaries

…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.
Copy link
Copy Markdown

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @leszek3737, you have reached your weekly rate limit of 2500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@github-actions github-actions bot added area/kernel Core kernel (scheduling, RBAC, workflows) area/runtime Agent loop, LLM drivers, WASM sandbox area/channels Messaging channel adapters labels Apr 13, 2026
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +1857 to +1861
let safe_name = if agent_name.len() > max_name_bytes {
&agent_name[..max_name_bytes]
} else {
&agent_name
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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.

Suggested change
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
};

Comment on lines +935 to +939
let action = if btn.action.len() > 64 {
btn.action[..64].to_string()
} else {
btn.action.clone()
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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>>,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The poll_contexts map is populated every time a poll is sent, but entries are never removed. This will lead to a gradual memory leak in long-running bot instances. Consider implementing a cleanup mechanism, such as using a cache with a TTL or removing entries after a reasonable timeout.

Comment on lines +688 to +694
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());
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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());
                    }

Comment on lines +2889 to +2900
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);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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
                ));
            }
        }

@leszek3737 leszek3737 closed this Apr 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/channels Messaging channel adapters area/kernel Core kernel (scheduling, RBAC, workflows) area/runtime Agent loop, LLM drivers, WASM sandbox

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant