Skip to content

feat(channels): add streaming typewriter card for Feishu#2501

Open
carlos999-hqsama wants to merge 2 commits intoagentscope-ai:mainfrom
carlos999-hqsama:feat/feishu-streaming-card
Open

feat(channels): add streaming typewriter card for Feishu#2501
carlos999-hqsama wants to merge 2 commits intoagentscope-ai:mainfrom
carlos999-hqsama:feat/feishu-streaming-card

Conversation

@carlos999-hqsama
Copy link
Copy Markdown
Contributor

@carlos999-hqsama carlos999-hqsama commented Mar 29, 2026

Description

Add streaming typewriter card output to the Feishu channel. AI replies are displayed character by character in real time using the Feishu Cardkit v1 Streaming Card API, instead of being sent all at once after generation completes.

Related Issue: Relates to #1296, #1549

Security Considerations: Uses existing tenant token from lark_oapi.core.token.TokenManager, no additional credentials required. Streaming is opt-in via environment variable.

Type of Change

  • New feature

Component(s) Affected

  • Channels (Feishu)
  • Documentation (website)
  • Tests

Checklist

  • I ran pre-commit run --all-files locally and it passes
  • If pre-commit auto-fixed files, I committed those changes and reran checks
  • I ran tests locally (pytest) and they pass
  • Documentation updated (if needed)
  • Ready for review

Testing

29 unit tests in tests/unit/channels/test_feishu_streaming.py covering:

  • _is_streaming_enabled: env var parsing (true/false/unset)
  • _next_stream_seq: monotonic sequence counter
  • _feishu_base_url: feishu.cn vs larksuite.com
  • _card_create: success, HTTP failure, feishu error code, schema validation
  • _card_send: success, HTTP failure
  • _card_update_text: success, HTTP failure, feishu error code, full-text push, URL validation
  • _card_close: success, streaming_mode=false, HTTP failure no-raise, exception no-raise
  • _extract_streaming_text: message/delta/content/nested/empty cases
  • _run_process_loop routing: disabled → normal path, enabled → streaming path

Local Verification Evidence

$ pre-commit run --files src/copaw/app/channels/feishu/channel.py tests/unit/channels/test_feishu_streaming.py
check yaml...............................................................Passed
check toml...............................................................Passed
fix end of files.........................................................Passed
mypy.....................................................................Passed
black....................................................................Passed
flake8...................................................................Passed
pylint...................................................................Passed

$ pytest tests/unit/channels/test_feishu_streaming.py -v
29 passed, 9 warnings in 3.37s

How to Enable

export FEISHU_STREAMING_ENABLED=true

Requires im:card:write and im:card:read permissions in the Feishu app config. When disabled or on API failure, output automatically falls back to normal messages.

Design Note

During streaming, the card may briefly display the model'''s thinking/reasoning process (e.g. tool-call planning). This is intentional — the thinking content is hidden once the final response is rendered and the card is finalized. This is by design, not a leak.

@github-actions
Copy link
Copy Markdown

Welcome to CoPaw! 🐾

Hi @carlos999-hqsama, this is your 1st Pull Request.

📋 About PR Template

To help maintainers review your PR faster, please make sure to include:

  • Description - What this PR does and why
  • Type of Change - Bug fix / Feature / Breaking change / Documentation / Refactoring
  • Component(s) Affected - Core / Console / Channels / Skills / CLI / Documentation / Tests / CI/CD / Scripts
  • Checklist:
    • Run and pass pre-commit run --all-files
    • Run and pass relevant tests (pytest or as applicable)
    • Update documentation if needed
  • Testing - How to test these changes
  • Local Verification Evidence:
    pre-commit run --all-files
    # paste summary result
    
    pytest
    # paste summary result

Complete PR information helps speed up the review process. You can edit the PR description to add these details.

🙌 Join Developer Community

Thanks so much for your contribution! We'd love to invite you to join the official CoPaw developer group! You can find the Discord and DingTalk group links under the "Developer Community" section on our docs page:
https://copaw.agentscope.io/docs/community

We truly appreciate your enthusiasm—and look forward to your future contributions! 😊

We'll review your PR soon.


Tip

⭐ If you find CoPaw useful, please give us a Star!

Star CoPaw

Staying ahead

Star CoPaw on GitHub and be instantly notified of new releases.

Your star helps more developers discover this project! 🐾

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request implements a streaming typewriter effect for the Feishu channel using the Cardkit v1 API, including documentation and unit tests. Key feedback includes fixing a potential TypeError during receiver info unpacking, removing redundant code and unused parameters, and replacing magic numbers with constants. It is also suggested to simplify boolean conditions for idiomatic Python and to add integration tests for the core streaming loop logic.

if obj == "message" and status == RunStatus.Completed:
final_text = text or ""
# If we had an active card, finalize it
if card_id and card_id != "":
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.

medium

The condition card_id and card_id != "" is redundant. In Python, an empty string is falsy, so you can simplify this to just card_id for conciseness.

Suggested change
if card_id and card_id != "":
if card_id:

Implement streaming card output using Feishu Cardkit v1 API:
- Create streaming card on first token, update in background (60ms interval)
- Feishu renders text character-by-character via streaming_config
- Auto fallback to normal message on API failure
- Controlled by FEISHU_STREAMING_ENABLED env var
- Supports both feishu.cn and larksuite.com domains

Co-authored-by: 贰柒 <27@copaw>
@carlos999-hqsama carlos999-hqsama force-pushed the feat/feishu-streaming-card branch from 960fbb1 to d4c7728 Compare March 29, 2026 16:04
- Remove unused _stop_updater function
- Remove unused final_text param from _card_close
- Simplify 'card_id and card_id != ""' to 'card_id' (both occurrences)
- Replace magic number 0.06 with _STREAMING_UPDATE_INTERVAL_SEC constant
- Update _card_close test call signatures
- Add integration tests for _run_process_loop_streaming lifecycle
@carlos999-hqsama
Copy link
Copy Markdown
Contributor Author

Gemini Review 反馈已全部修复

# 严重度 问题 修复方式
1 🔴 Critical _get_receive_for_send 返回None时解包崩溃 代码已有 if not recv 防护,确认无误报
2 🟠 High _stop_updater 未使用 已删除
3 🟠 High _run_process_loop_streaming 无测试 新增2个集成测试(正常生命周期 + 卡片创建失败降级)
4 🟡 Medium _card_closefinal_text 未使用 已移除参数,测试同步更新
5 🟡 Medium card_id and card_id != "" 冗余(×2) 两处均已简化为 card_id
6 🟡 Medium 0.06 魔法数字 提取为常量 _STREAMING_UPDATE_INTERVAL_SEC

31个测试全部通过,已push。

@carlos999-hqsama
Copy link
Copy Markdown
Contributor Author

Gemini Review 各项修复对照

1. 🔴 Critical — _get_receive_for_send 返回None时解包崩溃
→ 已确认代码中两处调用均有 if not recv 防护,无需额外修改。

2. 🟠 High — _stop_updater 未使用
→ 已删除(af3d40e)

3. 🟠 High — _run_process_loop_streaming 无测试覆盖
→ 新增2个集成测试:正常生命周期 + 卡片创建失败降级(af3d40e)

4. 🟡 Medium — _card_closefinal_text 未使用
→ 已移除参数,测试同步更新(af3d40e)

5. 🟡 Medium — card_id and card_id != "" 冗余(两处)
→ 均已简化为 card_id(af3d40e)

6. 🟡 Medium — 0.06 魔法数字
→ 提取为常量 _STREAMING_UPDATE_INTERVAL_SEC(af3d40e)

31个测试全部通过。请帮忙逐条标记 Resolved,感谢。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant