---
read_when:
    - Ви змінюєте форматування Markdown або розбиття на фрагменти для вихідних каналів
    - Ви додаєте новий форматувач каналу або зіставлення стилів
    - Ви налагоджуєте регресії форматування в різних каналах
summary: Конвеєр форматування Markdown для вихідних каналів
title: Форматування Markdown
x-i18n:
    generated_at: "2026-05-12T12:50:39Z"
    model: gpt-5.5
    provider: openai
    source_hash: 8db92aaf1063ebcbd8630dfcb8ca0a4e9eeb1c64f5b8868bf11c836777180515
    source_path: concepts/markdown-formatting.md
    workflow: 16
---

OpenClaw форматує вихідний Markdown, перетворюючи його на спільне проміжне
представлення (IR) перед рендерингом канально-специфічного виводу. IR зберігає
вихідний текст без змін і водночас переносить діапазони стилів/посилань, щоб
розбиття на фрагменти й рендеринг залишалися узгодженими між каналами.

## Цілі

- **Узгодженість:** один етап парсингу, кілька рендерерів.
- **Безпечне розбиття на фрагменти:** розділяти текст перед рендерингом, щоб
  вбудоване форматування ніколи не ламалося між фрагментами.
- **Відповідність каналу:** відображати той самий IR у Slack mrkdwn, Telegram HTML і
  діапазони стилів Signal без повторного парсингу Markdown.

## Конвеєр

1. **Парсинг Markdown -> IR**
   - IR — це звичайний текст плюс діапазони стилів (жирний/курсив/закреслений/код/спойлер) і діапазони посилань.
   - Зміщення — у кодових одиницях UTF-16, щоб діапазони стилів Signal збігалися з його API.
   - Таблиці парсяться лише тоді, коли канал вмикає перетворення таблиць.
2. **Розбиття IR на фрагменти (спочатку форматування)**
   - Розбиття відбувається на тексті IR перед рендерингом.
   - Вбудоване форматування не розривається між фрагментами; діапазони нарізаються для кожного фрагмента.
3. **Рендеринг для кожного каналу**
   - **Slack:** токени mrkdwn (жирний/курсив/закреслений/код), посилання як `<url|label>`.
   - **Telegram:** HTML-теги (`<b>`, `<i>`, `<s>`, `<code>`, `<pre><code>`, `<a href>`).
   - **Signal:** звичайний текст + діапазони `text-style`; посилання стають `label (url)`, коли мітка відрізняється.

## Приклад IR

Вхідний Markdown:

```markdown
Hello **world** - see [docs](https://docs.openclaw.ai).
```

IR (схематично):

```json
{
  "text": "Hello world - see docs.",
  "styles": [{ "start": 6, "end": 11, "style": "bold" }],
  "links": [{ "start": 19, "end": 23, "href": "https://docs.openclaw.ai" }]
}
```

## Де це використовується

- Вихідні адаптери Slack, Telegram і Signal виконують рендеринг з IR.
- Інші канали (WhatsApp, iMessage, Microsoft Teams, Discord) все ще використовують звичайний текст або
  власні правила форматування, із перетворенням таблиць Markdown перед
  розбиттям на фрагменти, коли це ввімкнено.

## Обробка таблиць

Таблиці Markdown не підтримуються узгоджено в усіх чат-клієнтах. Використовуйте
`markdown.tables`, щоб керувати перетворенням для кожного каналу (і кожного облікового запису).

- `code`: рендерити таблиці як блоки коду (типово для більшості каналів).
- `bullets`: перетворювати кожен рядок на марковані пункти (типово для Matrix, Signal і WhatsApp).
- `off`: вимкнути парсинг і перетворення таблиць; необроблений текст таблиці проходить без змін.

Ключі конфігурації:

```yaml
channels:
  discord:
    markdown:
      tables: code
    accounts:
      work:
        markdown:
          tables: off
```

## Правила розбиття на фрагменти

- Обмеження фрагментів надходять з адаптерів/конфігурації каналу й застосовуються до тексту IR.
- Блоки коду зберігаються як єдиний блок із кінцевим символом нового рядка, щоб канали
  рендерили їх коректно.
- Префікси списків і префікси блокових цитат є частиною тексту IR, тому розбиття
  не розділяє їх посередині префікса.
- Вбудовані стилі (жирний/курсив/закреслений/вбудований код/спойлер) ніколи не розриваються між
  фрагментами; рендерер повторно відкриває стилі всередині кожного фрагмента.

Якщо потрібно більше інформації про поведінку розбиття на фрагменти між каналами, див.
[Стримінг + розбиття на фрагменти](/uk/concepts/streaming).

## Політика посилань

- **Slack:** `[label](url)` -> `<url|label>`; голі URL залишаються голими. Автопосилання
  вимкнено під час парсингу, щоб уникнути подвійного посилання.
- **Telegram:** `[label](url)` -> `<a href="url">label</a>` (режим парсингу HTML).
- **Signal:** `[label](url)` -> `label (url)`, якщо мітка не збігається з URL.

## Спойлери

Маркери спойлерів (`||spoiler||`) парсяться лише для Signal, де вони відображаються в
діапазони стилю SPOILER. Інші канали обробляють їх як звичайний текст.

## Як додати або оновити форматер каналу

1. **Парсити один раз:** використовуйте спільний допоміжний метод `markdownToIR(...)` з відповідними для каналу
   параметрами (автопосилання, стиль заголовків, префікс блокової цитати).
2. **Рендерити:** реалізуйте рендерер із `renderMarkdownWithMarkers(...)` і
   мапою маркерів стилів (або діапазонами стилів Signal).
3. **Розбити на фрагменти:** викличте `chunkMarkdownIR(...)` перед рендерингом; рендеріть кожен фрагмент.
4. **Під’єднати адаптер:** оновіть вихідний адаптер каналу, щоб він використовував новий механізм розбиття
   і рендерер.
5. **Протестувати:** додайте або оновіть тести форматування й тест вихідної доставки, якщо
   канал використовує розбиття на фрагменти.

## Поширені помилки

- Токени Slack у кутових дужках (`<@U123>`, `<#C123>`, `<https://...>`) потрібно
  зберігати; безпечно екрануйте необроблений HTML.
- Telegram HTML вимагає екранування тексту поза тегами, щоб уникнути зламаної розмітки.
- Діапазони стилів Signal залежать від зміщень UTF-16; не використовуйте зміщення за кодовими точками.
- Зберігайте кінцеві символи нового рядка для блоків коду з огорожами, щоб закривальні маркери були
  на окремому рядку.

## Пов’язане

<CardGroup cols={2}>
  <Card title="Стримінг і розбиття на фрагменти" href="/uk/concepts/streaming" icon="bars-staggered">
    Поведінка вихідного стримінгу, межі фрагментів і канально-специфічна доставка.
  </Card>
  <Card title="Системний prompt" href="/uk/concepts/system-prompt" icon="message-lines">
    Що модель бачить перед розмовою, включно з впровадженими файлами робочої області.
  </Card>
</CardGroup>
