← Back to PRs

#11084: feat(feishu): implement CardKit streaming card output with typewriter effect

by liuhui201069 open 2026-02-07 11:12 View on GitHub →
channel: feishu stale
## Problem Feishu `im.message.patch` API has an edit count limit (~20-30 edits), which breaks block streaming for long responses — updates silently stop. ## Solution Use Feishu **CardKit API** for streaming card updates: 1. Send an interactive card with `streaming_mode: true` and a named `element_id` 2. Convert `message_id` → `card_id` via `cardkit.v1.card.idConvert` 3. Stream-update element content via `cardkit.v1.cardElement.content` (typewriter effect, unlimited updates) ### Changes **`send.ts`** - `sendStreamingCardFeishu()` — sends streaming card + idConvert - `streamUpdateCardElementFeishu()` — stream updates card element with sequence **`reply-dispatcher.ts`** - CardKit streaming state (cardId, sequence, accumulatedText) - Accumulated text sending (full replacement per update) - Auto-creates new card after 3000 chars to avoid display issues - Graceful fallback to regular card send on CardKit failure ## Requirements - Feishu app permission: `cardkit:card:write` - Config: `channels.feishu.streaming: true`, `blockStreamingDefault: "on"` - Recommended: `blockStreamingChunk.minChars: 100, maxChars: 300` for smooth streaming ## Testing Tested with long streaming responses on Feishu mobile — typewriter effect works smoothly. Auto card splitting at 3000 chars works correctly. Fallback triggers properly when CardKit API is unavailable. <!-- greptile_comment --> <h2>Greptile Overview</h2> <h3>Greptile Summary</h3> This PR adds Feishu CardKit-based “streaming card” support to avoid the `im.message.patch` edit-count limit during long block streaming. It introduces new helpers in `extensions/feishu/src/send.ts` to send an interactive card with `streaming_mode: true`, convert `message_id → card_id`, and then stream-update a specific markdown element via `cardkit.v1.cardElement.content`. The reply dispatcher is updated to maintain CardKit streaming state (cardId/sequence/accumulated text) and to split into new cards after a character threshold, with a fallback back to regular card/text sending on errors. Key things to verify before merge are correctness of the CardKit ID conversion inputs and that the dispatcher’s streaming state is properly scoped/reset so non-streaming replies don’t accidentally append into a prior streamed card, and that the fallback path doesn’t resend stale accumulated content after an error. <h3>Confidence Score: 3/5</h3> - This PR is mergeable after fixing a few state/flow bugs in the new CardKit streaming path. - The overall approach is coherent, but there are concrete correctness issues: `idConvert` can be called with a placeholder message id, and the dispatcher’s streaming state is not clearly gated/reset, which can lead to duplicated/incorrect message content after failures or across non-streaming sends. - extensions/feishu/src/send.ts; extensions/feishu/src/reply-dispatcher.ts <!-- greptile_other_comments_section --> <sub>(2/5) Greptile learns from your feedback when you react with thumbs up/down!</sub> <!-- /greptile_comment -->

Most Similar PRs