refactor(feishu): simplify tool hint to append-only, delegate to send_delta for throttling

- Make tool_hint_prefix configurable in FeishuConfig (default: 🔧)
- Delegate tool hint card updates from send() to send_delta() so hints
  automatically benefit from _STREAM_EDIT_INTERVAL throttling
- Fix staticmethod calls to use self.__class__ instead of self
- Document all supported metadata keys in send_delta docstring
- Add test for empty/whitespace-only tool hint with active stream buffer
This commit is contained in:
chengyongru
2026-04-10 12:29:43 +08:00
committed by Xubin Ren
parent 049ce9baae
commit 6fd2511c8a
2 changed files with 50 additions and 39 deletions
+22 -23
View File
@@ -251,6 +251,7 @@ class FeishuConfig(Base):
allow_from: list[str] = Field(default_factory=list)
react_emoji: str = "THUMBSUP"
done_emoji: str | None = None # Emoji to show when task is completed (e.g., "DONE", "OK")
tool_hint_prefix: str = "\U0001f527" # Prefix for inline tool hints (default: 🔧)
group_policy: Literal["open", "mention"] = "mention"
reply_to_message: bool = False # If True, bot replies quote the user's original message
streaming: bool = True
@@ -267,7 +268,6 @@ class _FeishuStreamBuf:
card_id: str | None = None
sequence: int = 0
last_edit: float = 0.0
tool_hint_len: int = 0
class FeishuChannel(BaseChannel):
@@ -1265,7 +1265,15 @@ class FeishuChannel(BaseChannel):
async def send_delta(
self, chat_id: str, delta: str, metadata: dict[str, Any] | None = None
) -> None:
"""Progressive streaming via CardKit: create card on first delta, stream-update on subsequent."""
"""Progressive streaming via CardKit: create card on first delta, stream-update on subsequent.
Supported metadata keys:
_stream_end: Finalize the streaming card.
_resuming: Mid-turn pause flush but keep the buffer alive.
_tool_hint: Delta is a formatted tool hint (for display only).
message_id: Original message id (used with _stream_end for reaction cleanup).
reaction_id: Reaction id to remove on stream end.
"""
if not self._client:
return
meta = metadata or {}
@@ -1287,7 +1295,6 @@ class FeishuChannel(BaseChannel):
# next segment appends to the same card.
buf = self._stream_bufs.get(chat_id)
if buf and buf.card_id and buf.text:
buf.tool_hint_len = 0
buf.sequence += 1
await loop.run_in_executor(
None, self._stream_update_text_sync, buf.card_id, buf.text, buf.sequence,
@@ -1297,7 +1304,6 @@ class FeishuChannel(BaseChannel):
buf = self._stream_bufs.pop(chat_id, None)
if not buf or not buf.text:
return
buf.tool_hint_len = 0
if buf.card_id:
buf.sequence += 1
await loop.run_in_executor(
@@ -1333,8 +1339,6 @@ class FeishuChannel(BaseChannel):
if buf is None:
buf = _FeishuStreamBuf()
self._stream_bufs[chat_id] = buf
if buf.tool_hint_len > 0:
buf.tool_hint_len = 0
buf.text += delta
if not buf.text.strip():
return
@@ -1377,22 +1381,17 @@ class FeishuChannel(BaseChannel):
return
buf = self._stream_bufs.get(msg.chat_id)
if buf and buf.card_id:
if buf.tool_hint_len > 0:
buf.text = buf.text[:-buf.tool_hint_len]
lines = self._format_tool_hint_lines(hint).split("\n")
formatted = "\n".join(f"🔧 {ln}" for ln in lines if ln.strip())
suffix = f"\n\n{formatted}\n\n"
buf.text += suffix
buf.tool_hint_len = len(suffix)
buf.sequence += 1
await loop.run_in_executor(
None, self._stream_update_text_sync,
buf.card_id, buf.text, buf.sequence,
)
else:
await self._send_tool_hint_card(
receive_id_type, msg.chat_id, hint
)
# Delegate to send_delta so tool hints get the same
# throttling (and card creation) as regular text deltas.
lines = self.__class__._format_tool_hint_lines(hint).split("\n")
delta = "\n\n" + "\n".join(
f"{self.config.tool_hint_prefix} {ln}" for ln in lines if ln.strip()
) + "\n\n"
await self.send_delta(msg.chat_id, delta)
return
await self._send_tool_hint_card(
receive_id_type, msg.chat_id, hint
)
return
# Determine whether the first message should quote the user's message.
@@ -1701,7 +1700,7 @@ class FeishuChannel(BaseChannel):
loop = asyncio.get_running_loop()
# Put each top-level tool call on its own line without altering commas inside arguments.
formatted_code = self._format_tool_hint_lines(tool_hint)
formatted_code = self.__class__._format_tool_hint_lines(tool_hint)
card = {
"config": {"wide_screen_mode": True},