Skip to content

Add --format flag for HTML/MarkdownV2 messages via FormattedSender capability#27

Open
hegelstad wants to merge 2 commits into
dcosson:mainfrom
hegelstad:feat/format-flag
Open

Add --format flag for HTML/MarkdownV2 messages via FormattedSender capability#27
hegelstad wants to merge 2 commits into
dcosson:mainfrom
hegelstad:feat/format-flag

Conversation

@hegelstad
Copy link
Copy Markdown

Summary

Wires the FormattedSender capability (added in 3839290) end-to-end so that
agents can send HTML or MarkdownV2 messages through bridges (currently
Telegram) via h2 send --format.

Before this PR, Telegram.SendFormatted() existed but had no caller — the
bridgeservice dispatcher only invoked bridge.Sender.Send(), so rich-text
delivery required bypassing h2 entirely and hitting the Bot API directly.

What changes

  • New bridge.FormattedSender capability interface, mirrored on the
    established bridge.TypingIndicator pattern.
  • New --format HTML|MarkdownV2 flag on h2 send, validated at the CLI.
  • New Format field on message.Request (omitempty — wire-backward-compatible).
  • Dispatcher in bridgeservice.sendOutbound feature-detects
    FormattedSender and calls SendFormatted() when --format is set.
  • Hard errors (not silent fallbacks) on misuse:
    • Unknown format value → rejected before dialing any socket.
    • --format on agent targets → rejected at the CLI (socketdir.Parse).
    • --format on a bridge that only implements Sender → rejected at
      dispatch with "<bridge>: does not support --format".
  • Agent-tag prefix ([from-agent] ) stays as plain text outside the
    formatted 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: new mockFormattedBridge,
    TestSendOutbound_FormatRoutedToSendFormatted,
    TestSendOutbound_FormatOnSenderOnlyBridge_Errors.
  • internal/bridge/telegram/telegram_test.go: TestSendFormatted_MarkdownV2
    complementing the existing HTML test.

Test plan

  • make check-nofix clean (gofmt + go vet + staticcheck)
  • make test passes
  • make test-external passes
  • Manual smoke: h2 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

  • Commit 3839290 (FormattedSender on the Telegram bridge) is included
    because it was never pushed to origin/main and the new plumbing
    depends on it. The two together form the complete feature; reviewing
    them as a unit is easier than splitting.
  • A WIP local stash (wip-streaming-2026-04) contained a half-finished
    version of this work entangled with unrelated streaming infrastructure.
    This PR reimplements the format-flag pieces cleanly on top of main
    without applying any of that stash.

🤖 Generated with Claude Code

dcosson and others added 2 commits May 25, 2026 11:03
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants