Skip to content

feat(feishu): render markdown tables as native Feishu card table components - #46727

Open
BiuDoom wants to merge 1 commit into
NousResearch:mainfrom
BiuDoom:feat/feishu-markdown-table-card
Open

feat(feishu): render markdown tables as native Feishu card table components#46727
BiuDoom wants to merge 1 commit into
NousResearch:mainfrom
BiuDoom:feat/feishu-markdown-table-card

Conversation

@BiuDoom

@BiuDoom BiuDoom commented Jun 15, 2026

Copy link
Copy Markdown

Problem

Feishu's post/md message format does not render GFM markdown tables (per Feishu's docs the md tag supports only a subset of markdown, and tables are not included). FeishuAdapter._build_outbound_payload currently detects any markdown table and downgrades the entire message to plain text, losing all formatting (bold, headings, lists) and showing raw | --- | pipes to the user.

Change

When outbound content contains a markdown table, build a Feishu card JSON 2.0 message with native table components, while non-table prose is preserved as markdown elements.

  • New helpers: _split_content_into_blocks (orders prose / table / fenced-code blocks; code fences are never parsed as tables), _parse_gfm_table (GFM -> columns/rows), _build_table_card (assembles the card).
  • Table cells use data_type: "markdown" so inline formatting (e.g. bold) renders; row_height: "auto" + row_max_height so long cells wrap instead of being truncated.
  • Respects Feishu hard limits (<=5 tables/card, <=50 columns); on any limit breach, parse failure, or exception, it falls back to the previous plain-text behavior, so a message is never dropped.
  • Existing post / text / approval-card paths are unchanged.

Tests

tests/gateway/test_feishu_table_card.py (17 tests): block splitting, GFM parsing, card structure, order preservation, fenced-pseudo-table exclusion, >5-table fallback, and _build_outbound_payload routing. Existing test_feishu.py (205) and test_feishu_approval_buttons.py (38) still pass.

Feishu's post/md format does not render GFM markdown tables, so FeishuAdapter._build_outbound_payload previously downgraded any message containing a table to plain text, losing all formatting and showing raw pipe characters to the user.

This builds a Feishu card JSON 2.0 message with native table components when outbound content contains a markdown table, while non-table prose is preserved as markdown elements. Table cells use data_type markdown so inline formatting renders; row_height auto + row_max_height prevent truncation. Respects Feishu limits (<=5 tables/card, <=50 columns) and falls back to plain text on any breach, parse failure, or exception, so a message is never dropped. Existing post/text/approval-card paths are unchanged.

Adds tests/gateway/test_feishu_table_card.py (17 tests).
@BiuDoom
BiuDoom force-pushed the feat/feishu-markdown-table-card branch from 870c028 to c1323e5 Compare June 15, 2026 15:55
@alt-glitch alt-glitch added type/feature New feature or request comp/gateway Gateway runner, session dispatch, delivery platform/feishu Feishu / Lark adapter P3 Low — cosmetic, nice to have duplicate This issue or pull request already exists labels Jun 15, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

Duplicate of #12114 — both render markdown tables via Feishu Card v2 native table components. #12114 is the earliest open PR using this mechanism, part of a large saturated cluster (#9588, #25453, #26429, #45907, others). Maintainer should pick the canonical implementation.

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the focused table-rendering implementation. The underlying issue remains on current main: plugins/platforms/feishu/adapter.py:4524-4534 still downgrades detected tables to plain text.

Problems

  • The branch at gateway/platforms/feishu.py:4582 returns interactive, but the current send and edit fallback paths only recover rejected post payloads (plugins/platforms/feishu/adapter.py:1913-1936 and :1963-1971). A rejected card would therefore fail instead of taking the claimed text fallback.
  • tests/gateway/test_feishu_table_card.py:209-228 duplicates the routing logic rather than invoking _build_outbound_payload, so it cannot catch drift in the production method.
  • The target adapter was relocated by 5600105478ffde29d7566b45421b100eaa29c4ef to plugins/platforms/feishu/adapter.py; this needs a port rather than applying the old-file diff directly.

Suggested changes

  • Port the implementation to the bundled adapter and test rejected interactive-card send/edit responses falling back to text.
  • Exercise the real adapter payload method in the routing tests.

Automated hermes-sweeper review.

try:
card_json = _build_table_card(content)
if card_json is not None:
return "interactive", card_json

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This changes the outbound type to interactive, but the send and edit fallback paths only recover rejected post payloads. Add an interactive-card rejection fallback there as well, with a regression test, so an API-invalid card does not turn into a failed delivery.

"""

def _call(self, content):
# The method is an instance method; replicate its logic via the helpers.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This copies _build_outbound_payload instead of exercising it. Instantiate the adapter minimally or use the existing fixture and call the real method, otherwise a later production change can silently diverge from this test.

@teknium1 teknium1 added sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/gateway Gateway runner, session dispatch, delivery duplicate This issue or pull request already exists P3 Low — cosmetic, nice to have platform/feishu Feishu / Lark adapter sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants