Add --format flag for HTML/MarkdownV2 messages via FormattedSender capability#27
Open
hegelstad wants to merge 2 commits into
Open
Add --format flag for HTML/MarkdownV2 messages via FormattedSender capability#27hegelstad wants to merge 2 commits into
hegelstad wants to merge 2 commits into
Conversation
Implement SendFormatted() on the Telegram bridge so that h2 send --format HTML passes parse_mode to the Telegram Bot API instead of sending HTML tags as literal text. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Telegram bridge has SendFormatted() but nothing calls it. This commit threads a parse-mode hint from the `h2 send` CLI through the wire protocol and bridge dispatcher so that bridges implementing the new FormattedSender capability interface receive the body via SendFormatted() instead of Send(). Changes: - Add FormattedSender capability interface in internal/bridge/bridge.go, mirroring the existing TypingIndicator pattern. - Add Format string field (omitempty) to message.Request so the value travels across the unix socket alongside the body. - Add --format=HTML|MarkdownV2 flag on `h2 send`, validated before any socket dial. Invalid values are rejected with a clear error. - Reject --format on agent targets at the CLI; the flag is only valid for bridge targets (resolved via socketdir.Parse on the socket name). - In bridgeservice.sendOutbound, when format is non-empty, feature-detect bridge.FormattedSender and call SendFormatted; bridges that only implement bridge.Sender produce a hard error (no silent downgrade). - Plumb --format through handleCloses so responses to expects-response triggers can use formatted delivery. - The agent-tag prefix ([from-agent] ) is kept as plain text outside the formatted body so HTML/Markdown parsers don't have to consider it. Caller is responsible for escaping body characters reserved by the chosen parse mode. Tests: - internal/cmd/send_test.go: validateFormat table, invalid-value rejection before dial, agent-target rejection. - internal/bridgeservice/service_test.go: mockFormattedBridge + tests that format routes to SendFormatted and that Sender-only bridges error on --format. - internal/bridge/telegram/telegram_test.go: SendFormatted MarkdownV2 case complementing the existing HTML test. Documented under docs/CHANGELOG.md Unreleased. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Wires the
FormattedSendercapability (added in 3839290) end-to-end so thatagents can send HTML or MarkdownV2 messages through bridges (currently
Telegram) via
h2 send --format.Before this PR,
Telegram.SendFormatted()existed but had no caller — thebridgeservice dispatcher only invoked
bridge.Sender.Send(), so rich-textdelivery required bypassing h2 entirely and hitting the Bot API directly.
What changes
bridge.FormattedSendercapability interface, mirrored on theestablished
bridge.TypingIndicatorpattern.--format HTML|MarkdownV2flag onh2 send, validated at the CLI.Formatfield onmessage.Request(omitempty — wire-backward-compatible).bridgeservice.sendOutboundfeature-detectsFormattedSenderand callsSendFormatted()when--formatis set.--formaton agent targets → rejected at the CLI (socketdir.Parse).--formaton a bridge that only implementsSender→ rejected atdispatch with
"<bridge>: does not support --format".[from-agent]) stays as plain text outside theformatted body. Callers are responsible for escaping body content for
the chosen parse mode.
Tests
internal/cmd/send_test.go:TestValidateFormat,TestSend_FormatFlagRejectsInvalidBeforeDial,TestSend_FormatFlagRejectsAgentTarget.internal/bridgeservice/service_test.go: newmockFormattedBridge,TestSendOutbound_FormatRoutedToSendFormatted,TestSendOutbound_FormatOnSenderOnlyBridge_Errors.internal/bridge/telegram/telegram_test.go:TestSendFormatted_MarkdownV2complementing the existing HTML test.
Test plan
make check-nofixclean (gofmt + go vet + staticcheck)make testpassesmake test-externalpassesh2 send <bridge> --format HTML "<b>hi</b>"renders bold in Telegram with
[agent]tag preserved as plain text.h2 send <bridge> --format Markdown "x"rejected with validation error.h2 send <agent> --format HTML "x"rejected with bridge-target error.Notes
3839290(FormattedSender on the Telegram bridge) is includedbecause it was never pushed to
origin/mainand the new plumbingdepends on it. The two together form the complete feature; reviewing
them as a unit is easier than splitting.
wip-streaming-2026-04) contained a half-finishedversion of this work entangled with unrelated streaming infrastructure.
This PR reimplements the format-flag pieces cleanly on top of
mainwithout applying any of that stash.
🤖 Generated with Claude Code