Skip to content

feat(feishu): Card 2.0 interactive cards for markdown content#12459

Closed
C-fog wants to merge 11 commits into
NousResearch:mainfrom
C-fog:feat/feishu-interactive-cards-2.0
Closed

feat(feishu): Card 2.0 interactive cards for markdown content#12459
C-fog wants to merge 11 commits into
NousResearch:mainfrom
C-fog:feat/feishu-interactive-cards-2.0

Conversation

@C-fog

@C-fog C-fog commented Apr 19, 2026

Copy link
Copy Markdown

Summary

Upgrade the Feishu adapter to use Card 2.0 schema and add interactive card rendering for Markdown content (tables, code blocks, headings, etc.).

What changed (5 commits)

Refactor + Bug fix:

  • Extract _send_chunk_with_fallback from send() — centralizes the post→text fallback logic into a reusable method
  • Fix: _build_outbound_payload now detects pure Markdown tables that were previously missed by _MARKDOWN_HINT_RE, causing them to render as raw | characters in text mode

Card 2.0 upgrade:

  • Upgrade approval cards (pending + resolved) from Card 1.0 to 2.0 schema
  • Add Card 2.0 rendering path for Markdown content with three-tier fallback: interactive → post → text
  • Opt-in via use_interactive_cards_for_markdown: true in Feishu config (default: false)

Why

Feishu post messages do not render Markdown tables — they display as raw | characters with no borders or alignment. Card 2.0 provides significantly better rendering for complex Markdown structures (tables, code blocks, headings, blockquotes, lists).

Post vs Card 2.0 rendering comparison:

Post vs Card 2.0

Top: Post message renders table as raw | characters with no structure. Bottom: Card 2.0 renders a proper table with borders and alignment.

Design decisions

  • Opt-in by defaultuse_interactive_cards_for_markdown defaults to false; existing users are unaffected
  • Narrowed trigger_COMPLEX_MARKDOWN_RE only matches block-level structures (tables, fenced code blocks, headings, blockquotes, lists, horizontal rules). Inline formatting (**bold**, *italic*, [links]()) continues to use post messages which handle them well
  • Size pre-check — Card payloads exceeding 30KB (_FEISHU_CARD_MAX_BYTES) are skipped before sending, avoiding wasted API calls. Falls back to post, then text
  • Callback compatibility — The approval card button format changes from "value": {...} to "behaviors": [{"type": "callback", "value": {...}}]. However, _on_card_action_trigger reads action.value from the SDK event object, which is format-agnostic — Card 2.0 behaviors is transparent at the Feishu API level. No changes needed to callback handling code
  • Edit cross-type fallbackedit_message also follows the interactive→post→text fallback chain. Cross-type editing is best-effort; in practice this rarely triggers since edit content matches the original send path

Files changed

  • gateway/platforms/feishu.py — ~300 lines changed
  • tests/gateway/test_feishu.py — 12 new test cases (116 total, all passing)

Test plan

  • All 116 existing Feishu adapter tests pass
  • Positive triggers: tables, code blocks, headings, blockquotes → Card 2.0
  • Negative triggers: bold, italic, links, math expressions → post (unchanged)
  • Oversized payload (>30KB) → falls back to post
  • Approval card buttons still functional after 2.0 migration
  • use_interactive_cards_for_markdown=false → no behavior change from upstream

C-fog and others added 11 commits April 19, 2026 13:17
Add an optional require_mention field to FeishuGroupRule (default True).
When set to False for a specific group, the @mention requirement is
skipped so the bot responds to all messages from allowed senders.

Recommended for private groups containing only the owner and the bot.
Cover four cases:
- require_mention=False accepts messages without @mention
- require_mention=True still requires @mention (default behavior)
- Groups without explicit rule still require @mention (backward compat)
- require_mention=False does not bypass sender allowlist/blacklist checks
The boot-md hook created a bare AIAgent() with no model, provider,
api_key or base_url. The internal auto-resolve chain discards the
resolved model, causing every boot-md API call to fail with:

    400 — 'model: The model code cannot be empty.'

Fix by passing the resolved model and runtime_kwargs through the
gateway:startup hook context, then forwarding them to AIAgent via
**runtime_kwargs — matching the pattern used by every other AIAgent
call site in the gateway.

Also move the gateway:startup emit to after channel directory build
so that hooks can safely use send_message during startup.

Refs: NousResearch#5239
- Add _count_md_tables() and _split_md_by_table_limit() helpers
- send(): pre-check table count, split into multiple messages when >5
- edit_message(): truncate to first 5 tables with notice when >5
- Improve fallback logging with error codes for diagnosis
- Add 11 unit tests covering counting, splitting, and truncation

Fixes: Card rejected by API when markdown content contains >5 tables,
blind fallback to post format causes garbled table rendering.
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 P3 Low — cosmetic, nice to have platform/feishu Feishu / Lark adapter type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants