Skip to content

Update feishu.py - #22316

Open
heyanyu wants to merge 1 commit into
NousResearch:mainfrom
heyanyu:main
Open

Update feishu.py#22316
heyanyu wants to merge 1 commit into
NousResearch:mainfrom
heyanyu:main

Conversation

@heyanyu

@heyanyu heyanyu commented May 9, 2026

Copy link
Copy Markdown

Title: fix: render markdown tables as interactive cards in Feishu gateway
Description:
Current behavior: when a message contains markdown tables, _build_outbound_payload detects them via _MARKDOWN_TABLE_RE and falls back to msg_type='text', which renders raw |...| markdown syntax literally.
Fix: add _build_card_payload() and helpers that parse markdown content with tables into Feishu interactive card messages using native table components. Table cells are plain text (Feishu limitation), but surrounding text renders normally via lark_md divs. Falls back to plain text on any exception.
Changes:
New module-level functions: _card_format_cell, _card_parse_table, _build_card_payload
_build_outbound_payload now returns ('interactive', card_json) for table content, with try/except fallback to plain text
send_message and edit_message fallback handlers extended to cover 'interactive' type alongside existing 'post'
Limitations:
Requires Feishu client v7.4+ for table component rendering
Table cells are plain text only (markdown stripped)
Max 5 tables per card (Feishu API limit)

PR: fix: render markdown tables as interactive cards in Feishu gateway 问题描述
当回复消息包含 markdown 表格时,当前 Feishu gateway 检测到 _MARKDOWN_TABLE_RE 匹配后,将整条消息降级为 msg_type='text'(纯文本)。 结果是表格显示为原始 |...| markdown 语法,用户无法阅读。

修改内容

  1. 新增模块级函数(gateway/platforms/feishu.py,~L165-285)

_card_format_cell() — 清理表格单元格内的 markdown 标记
_card_parse_table() — 将 markdown 表格解析为 Feishu table 组件结构 _build_card_payload() — 将含表格的 markdown 内容转为 interactive card JSON
2. 修改 _build_outbound_payload(~L4124)

检测到表格时,返回 ("interactive", card_json) 而非 ("text", plain_text)。 任何异常自动 fallback 到纯文本,确保消息不丢失。

  1. 发送管道的 fallback(3处)

send_message 和 edit_message 中对 "interactive" 类型添加了与 "post" 相同的 API 拒绝回退机制。

设计说明
非表格内容(标题、列表、引用、代码块等)渲染在 lark_md div 中,保留富文本格式
表格单元仅支持纯文本(Feishu table 组件限制)
所有新增代码为独立模块级函数,不侵入现有类结构
单卡片最多 5 个 table 组件(Feishu API 限制)
需要 Feishu 客户端 v7.4+
测试覆盖
已在本地验证 9 种场景:

纯表格
文字 + 表格混合
多表格
表格 + 富文本(粗体/引用/链接)
纯文本(无表格,走原路径)
纯 markdown 无表格(走原路径)
多行列表格
富文本 + 表格 + 分隔线 + 注释
长表格(11行 → page_size clamp)
操作步骤
在 GitHub 网页上提交
打开 https://github.com/NousResearch/hermes-agent
点右上角 Fork → 创建自己的 fork
在 fork 中进入 gateway/platforms/feishu.py
点右上角 ✏️ Edit this file
在 ~L165(_POST_CONTENT_INVALID_RE 之后)插入以下代码
修改 ~L4124 的 _build_outbound_payload 方法
修改 ~L1840 和 ~L1890 的 fallback 判断
提交 → Contribute → Open Pull Request
补丁文件
补丁文件位于:/tmp/feishu_table_fix.diff

也可在下方直接查看完整 diff。

What does this PR do?

Related Issue

Fixes #

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 🔒 Security fix
  • 📝 Documentation update
  • ✅ Tests (adding or improving test coverage)
  • ♻️ Refactor (no behavior change)
  • 🎯 New skill (bundled or hub)

Changes Made

How to Test

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run pytest tests/ -q and all tests pass
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform:

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — or N/A
  • I've updated cli-config.yaml.example if I added/changed config keys — or N/A
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — or N/A
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide — or N/A
  • I've updated tool descriptions/schemas if I changed tool behavior — or N/A

For New Skills

  • This skill is broadly useful to most users (if bundled) — see Contributing Guide
  • SKILL.md follows the standard format (frontmatter, trigger conditions, steps, pitfalls)
  • No external dependencies that aren't already available (prefer stdlib, curl, existing Hermes tools)
  • I've tested the skill end-to-end: hermes --toolsets skills -q "Use the X skill to do Y"

Screenshots / Logs

PR: fix: render markdown tables as interactive cards in Feishu gateway
问题描述
当回复消息包含 markdown 表格时,当前 Feishu gateway 检测到 _MARKDOWN_TABLE_RE 匹配后,将整条消息降级为 msg_type='text'(纯文本)。 结果是表格显示为原始 |...| markdown 语法,用户无法阅读。

修改内容
1. 新增模块级函数(gateway/platforms/feishu.py,~L165-285)

_card_format_cell() — 清理表格单元格内的 markdown 标记
_card_parse_table() — 将 markdown 表格解析为 Feishu table 组件结构
_build_card_payload() — 将含表格的 markdown 内容转为 interactive card JSON
2. 修改 _build_outbound_payload(~L4124)

检测到表格时,返回 ("interactive", card_json) 而非 ("text", plain_text)。 任何异常自动 fallback 到纯文本,确保消息不丢失。

3. 发送管道的 fallback(3处)

send_message 和 edit_message 中对 "interactive" 类型添加了与 "post" 相同的 API 拒绝回退机制。

设计说明
非表格内容(标题、列表、引用、代码块等)渲染在 lark_md div 中,保留富文本格式
表格单元仅支持纯文本(Feishu table 组件限制)
所有新增代码为独立模块级函数,不侵入现有类结构
单卡片最多 5 个 table 组件(Feishu API 限制)
需要 Feishu 客户端 v7.4+
测试覆盖
已在本地验证 9 种场景:

纯表格
文字 + 表格混合
多表格
表格 + 富文本(粗体/引用/链接)
纯文本(无表格,走原路径)
纯 markdown 无表格(走原路径)
多行列表格
富文本 + 表格 + 分隔线 + 注释
长表格(11行 → page_size clamp)
操作步骤
在 GitHub 网页上提交
打开 https://github.com/NousResearch/hermes-agent
点右上角 Fork → 创建自己的 fork
在 fork 中进入 gateway/platforms/feishu.py
点右上角 ✏️ Edit this file
在 ~L165(_POST_CONTENT_INVALID_RE 之后)插入以下代码
修改 ~L4124 的 _build_outbound_payload 方法
修改 ~L1840 和 ~L1890 的 fallback 判断
提交 → Contribute → Open Pull Request
补丁文件
补丁文件位于:/tmp/feishu_table_fix.diff

也可在下方直接查看完整 diff。

@liuhao1024 liuhao1024 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.

Good improvement — using interactive cards with native table components is much better than falling back to plain text. Two things I noticed:

1. Empty cells get dropped, causing column misalignment

In `_card_parse_table`:

```python
cells = [c.strip() for c in s.strip("|").split("|")]
cells = [c for c in cells if c] # ← drops empty cells
```

For a table like `| A | | B |` (empty middle cell), this produces `["A", "B"]` instead of `["A", "", "B"]`. The row data then maps to the wrong columns — `B` ends up in `col_1` instead of `col_2`.

Fix: Remove the empty-cell filter and instead align by column count:

```python
cells = [c.strip() for c in s.strip("|").split("|")]
row = {}
for i, cell in enumerate(cells):
if i < len(columns):
row[columns[i]["name"]] = _card_format_cell(cell)
```

2. Accidental leading whitespace on line 1

The diff shows extra whitespace being added at the very top of the file (before the docstring). Likely an editor artifact:

```diff
+
"""
Feishu/Lark platform adapter.
```

@alt-glitch alt-glitch added type/bug Something isn't working P3 Low — cosmetic, nice to have comp/gateway Gateway runner, session dispatch, delivery platform/feishu Feishu / Lark adapter duplicate This issue or pull request already exists labels May 9, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

Duplicate of #12114 — same approach (Feishu Card v2 table component for markdown tables). Multiple competing PRs exist for this feature (#20152, #7704, #18155, #21000).

@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 addressing a real Feishu rendering limitation. Current main still deliberately falls back to text for Markdown tables at plugins/platforms/feishu/adapter.py:4524-4534, so the native-card direction remains relevant.

Problems

  • The PR targets gateway/platforms/feishu.py, but current main migrated the live adapter to plugins/platforms/feishu/adapter.py in 552adbe0827c32df8ed9bb19e908c26eff43add7; this needs a port rather than a clean cherry-pick.
  • gateway/platforms/feishu.py:218 drops empty cells before positional mapping, so | A | | B | shifts B into the wrong column. This confirms the earlier review feedback.
  • The proposed interactive fallback remains gated by the literal post-type error expression at gateway/platforms/feishu.py:1842-1855; other interactive-card rejections do not fall back.
  • The builder appends every matched table (gateway/platforms/feishu.py:233-265) despite the stated five-table limit, and the PR adds no tests.

Suggested changes

  • Port to plugins/platforms/feishu/adapter.py, preserve empty cells, define an interactive-card error fallback, and add payload/send/edit tests for empty cells, multi-table input, and rejection fallback.
  • Validate the generated Card v2 payload with a real Feishu bot before merge.

Automated hermes-sweeper review.

continue
if s.startswith("|") and s.endswith("|"):
cells = [c.strip() for c in s.strip("|").split("|")]
cells = [c for c in cells if c]

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.

Dropping empty cells shifts every subsequent value left. Preserve the empty strings and map cells by their original index; add a regression case for | A | | B |.

)
except Exception as exc:
if msg_type != "post" or not _POST_CONTENT_INVALID_RE.search(str(exc)):
if msg_type not in ("post", "interactive") or not _POST_CONTENT_INVALID_RE.search(str(exc)):

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 still requires a literal post-type validation error before falling back. An interactive card rejected with any other error bypasses the advertised plain-text fallback; define an interactive-specific rejection condition and cover it with a test.

elements = []
parts = []
last_end = 0
for match in _CARD_TABLE_RE.finditer(content):

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.

The PR description states a maximum of five table components per card, but this loop processes every match. Enforce or split at that limit and add a multi-table regression 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 area/install-update Installer, updater, packaging, wheels, doctor labels Jul 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/install-update Installer, updater, packaging, wheels, doctor 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/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants