メインコンテンツへスキップ

Markdown フォーマット

OpenClaw は、アウトバウンドの Markdown をチャンネル固有の出力にレンダリングする前に、共通の中間表現(IR)へ変換することでフォーマットします。IR はソース テキストを保持したまま、スタイル/リンクのスパンを保持するため、チャンク分割とレンダリングをチャンネル間で一貫させることができます。 16. IR はソーステキストをそのまま保持しつつ、スタイル/リンクのスパンを保持するため、チャンキングやレンダリングをチャンネル間で一貫させることができます。

目標

  • 一貫性: 1 回のパース、複数のレンダラー。
  • 安全なチャンク分割: レンダリング前にテキストを分割し、インライン フォーマットがチャンクをまたいで壊れないようにします。
  • チャンネル適合: 同一の IR を、Markdown の再パースなしで Slack の mrkdwn、Telegram の HTML、Signal のスタイル レンジにマッピングします。

パイプライン

  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:
Hello **world** — see [docs](https://docs.openclaw.ai).
IR(模式図):
{
  "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、MS Teams、Discord)は、引き続きプレーン テキストまたは独自のフォーマット ルールを使用します。Markdown のテーブル変換が有効な場合は、チャンク分割の前に適用されます。

テーブルの取り扱い

Markdown テーブルは、チャット クライアント間で一貫してサポートされていません。チャンネル(およびアカウント)ごとの変換制御には markdown.tables を使用します。 markdown.tables を使用して、チャンネルごとの変換を制御します。 markdown.tables を使用して、チャンネルごとの変換を制御します。
  • code: テーブルをコード ブロックとしてレンダリングします(多くのチャンネルの既定)。
  • bullets: 各行を箇条書きに変換します(Signal + WhatsApp の既定)。
  • off: テーブルのパースと変換を無効化し、生のテーブル テキストをそのまま通過させます。
設定キー:
channels:
  discord:
    markdown:
      tables: code
    accounts:
      work:
        markdown:
          tables: off

チャンク分割ルール

  • チャンク制限はチャンネル アダプター/設定から取得され、IR テキストに適用されます。
  • コード フェンスは、チャンネルが正しくレンダリングできるよう、末尾の改行を含む単一ブロックとして保持されます。
  • リスト プレフィックスと引用(blockquote)プレフィックスは IR テキストの一部であるため、チャンク分割でプレフィックスの途中が分割されることはありません。
  • インライン スタイル(太字/斜体/取り消し線/インライン コード/スポイラー)は、チャンクをまたいで分割されることはありません。レンダラーは各チャンク内でスタイルを再オープンします。
チャンネル間のチャンク分割の挙動について詳しくは、 Streaming + chunking を参照してください。

リンク ポリシー

  • Slack: [label](url) -> <url|label>。裸の URL はそのまま残ります。二重リンクを避けるため、パース中は自動リンクを無効化します。 ダブルリンクを避けるため、パース中にオートリンク が無効になっています。 ダブルリンクを避けるため、パース中にオートリンク が無効になっています。
  • Telegram: [label](url) -> <a href="url">label</a>(HTML パース モード)。
  • Signal: ラベルが URL と一致しない場合、[label](url) -> label (url) になります。

スポイラー

スポイラー マーカー(||spoiler||)は Signal のみでパースされ、SPOILER のスタイル レンジにマッピングされます。その他のチャンネルではプレーン テキストとして扱われます。 他のチャンネルはそれらをプレーンテキストとして扱います。 他のチャンネルはそれらをプレーンテキストとして扱います。

チャンネル フォーマッターを追加または更新する方法

  1. 一度だけパース: チャンネルに適したオプション(自動リンク、見出しスタイル、引用プレフィックス)で、共有の markdownToIR(...) ヘルパーを使用します。
  2. レンダリング: renderMarkdownWithMarkers(...) とスタイル マーカー マップ(または Signal のスタイル レンジ)を用いてレンダラーを実装します。
  3. チャンク分割: レンダリング前に chunkMarkdownIR(...) を呼び出し、各チャンクをレンダリングします。
  4. アダプターの接続: 新しいチャンク分割器とレンダラーを使用するよう、チャンネルのアウトバウンド アダプターを更新します。
  5. テスト: フォーマット テストを追加または更新し、チャンネルがチャンク分割を使用する場合はアウトバウンド配信テストを追加します。

一般的な gotcha

  • Slack の山括弧トークン(<@U123><#C123><https://...>)は保持する必要があります。生の HTML は安全にエスケープしてください。
  • Telegram の HTML では、マークアップ破損を避けるため、タグ外のテキストをエスケープする必要があります。
  • Signal のスタイル レンジは UTF-16 オフセットに依存します。コード ポイントのオフセットは使用しないでください。
  • フェンス付きコード ブロックでは、閉じマーカーが単独の行に配置されるよう、末尾の改行を保持してください。