AI models output standard markdown, but messaging platforms use their own formatting syntax. The IMessageFormatter interface provides a per-channel normalization pipeline that converts markdown to platform-native formatting before messages are sent.
WhatsApp uses custom delimiters instead of standard markdown:
| Markdown | Rendered As | |
|---|---|---|
**bold** |
*bold* |
bold |
_italic_ |
_italic_ |
italic |
~~strike~~ |
~strike~ |
|
`code` |
`code` |
code |
``` blocks |
``` blocks |
code block |
# Header |
*Header* |
Header |
- item |
• item |
bullet |
[text](url) |
text (url) |
link |
IMessageFormatter (Lis.Core)
↑
└── WhatsAppFormatter (Lis.Channels/WhatsApp)
└── Injected into WhatsAppClient via DI
AI Response → ResponseDirectives.Parse() → IMessageFormatter.Format() → GOWA → WhatsApp
↑
WhatsAppFormatter
The formatter runs inside WhatsAppClient.SendMessageAsync(), so:
- The DB stores the original markdown (better for AI context window)
- WhatsApp receives the platform-formatted version
Code blocks (fenced and inline) are extracted into placeholders before any transformations run, then restored at the end. This ensures formatting inside code examples is never modified.
- Create a class implementing
IMessageFormatterin the channel's namespace - Register it in the channel's
Add*()setup method - Inject into the channel's
IChannelClientimplementation
Example for a hypothetical Telegram channel:
public sealed class TelegramFormatter : IMessageFormatter
{
public string Format(string content) { /* ... */ }
}