feat(feishu): support markdown tables via CardKit table element (JSON 2.0 schema) - #31038
feat(feishu): support markdown tables via CardKit table element (JSON 2.0 schema)#31038wangzxjh wants to merge 1 commit into
Conversation
… 2.0 schema) Feishu post-type messages do not render markdown tables — they display as raw | characters. This adds detection of code blocks and markdown tables in _build_outbound_payload(), routing them to interactive cards (JSON 2.0 schema) instead of text or post. Changes: - _build_card_payload_from_blocks(): builds a JSON 2.0 card with mixed markdown + table elements - _build_card_table_element(): parses markdown tables into native CardKit table components with proper columns and rows - _split_into_card_elements(): interleaves text and table blocks preserving original order - _get_tenant_access_token() + _send_interactive_via_rest(): sends interactive cards via direct REST API (lark-oapi SDK does not handle JSON 2.0 card schema correctly for msg_type=interactive) - _feishu_send_with_retry(): routes msg_type=interactive to REST path - _build_outbound_payload(): detects code blocks and markdown tables and returns interactive cards instead of text/post Closes NousResearch#27695
teknium1
left a comment
There was a problem hiding this comment.
Thanks for pursuing native Feishu table rendering; the current active adapter still downgrades tables to plain text at plugins/platforms/feishu/adapter.py:4524-4530, so the underlying issue remains.
Problems
- This patch edits
gateway/platforms/feishu.py, but5600105478ffde29d7566b45421b100eaa29c4efmoved the active adapter toplugins/platforms/feishu/adapter.py; it needs a port. - The new REST path accepts
reply_toandmetadatabut sends only achat_idcreate request (gateway/platforms/feishu.py:1935-1963). The active path preserves replies and topic routing inplugins/platforms/feishu/adapter.py:4562-4601. urlopen()is invoked directly inside async methods (gateway/platforms/feishu.py:1929,:1974), which can block the adapter loop.edit_message()selectsinteractivethrough the changed payload builder (gateway/platforms/feishu.py:2081-2084), but the REST workaround and interactive fallback cover only sends.
Suggested changes
- Port this to the bundled plugin and preserve its reply/thread, retry, and async execution behavior.
- Add send/edit tests for tables, fenced code containing table syntax, reply/topic routing, and interactive rejection fallbacks.
Automated hermes-sweeper review.
| receive_id_type = "open_id" if chat_id.startswith("ou_") else "chat_id" | ||
| url = f"https://{domain}/open-apis/im/v1/messages?receive_id_type={receive_id_type}" | ||
|
|
||
| body = { |
There was a problem hiding this comment.
reply_to and metadata are accepted by this method but never influence this create-message request. The active sender uses them to route normal replies and topic messages (plugins/platforms/feishu/adapter.py:4562-4601); preserve those paths for interactive cards or table replies will be posted at the chat root.
| }, | ||
| ) | ||
| try: | ||
| resp = json.loads(_ur.urlopen(req, timeout=15).read()) |
There was a problem hiding this comment.
This synchronous urlopen() runs directly in an async method, so a slow Feishu request blocks the adapter event loop for up to 15 seconds. Run the blocking REST operation off-loop and retain the existing retry semantics.
| # 'md' elements do not render tables (message appears blank), and code | ||
| # blocks render much better in card markdown elements. | ||
| if "```" in content or _MARKDOWN_TABLE_RE.search(content): | ||
| return "interactive", _build_card_code_payload(content) |
There was a problem hiding this comment.
edit_message() also consumes _build_outbound_payload() but continues to call the SDK update path; this PR's REST workaround and interactive fallback only cover sends. Add a supported interactive update path plus a post/text fallback before routing edited table content here.
| # Regex to match a full markdown table block | ||
| # Matches the complete block: header line + separator line + data lines. | ||
| # Each line is captured as a whole row, then parsed by _build_card_table_element. | ||
| _TABLE_BLOCK_RE = re.compile( |
There was a problem hiding this comment.
This multiline regex has no fence awareness. A fenced code example containing a valid pipe table will be extracted and rendered as a native table, changing the code block's content. Parse fenced blocks before recognizing tables and add coverage for this case.
Problem
Feishu
posttype messages do not render markdown tables — they display as raw|characters, making table-formatted data unreadable.Solution
Detect code blocks (
`) and markdown tables via regex and route them to a JSON 2.0 interactive card with native CardKittableelements instead ofpostor plaintext.Key changes
_build_card_payload_from_blocks()— splits content into markdown and table elements, builds a JSON 2.0 card_build_card_table_element()— parses markdown tables into CardKittablecomponents_split_into_card_elements()— interleaves text and table blocks preserving original order_send_interactive_via_rest()— sends interactive cards via direct REST API (lark-oapi SDK does not handle JSON 2.0 schema correctly formsg_type=interactive)_feishu_send_with_retry()— routesmsg_type=interactiveto REST path_build_outbound_payload()— detects code blocks and markdown tables, returns interactive cardsBackwards Compatibility
_build_card_code_payload()kept as backward-compat aliasCloses #27695