mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-04-10 05:03:41 +00:00
fix(feishu): prevent tool hint stacking and clean hints on stream_end
Three fixes for inline tool hints: 1. Consecutive tool hints now replace the previous one instead of stacking — the old suffix is stripped before appending the new one. 2. When _resuming flushes the buffer, any trailing tool hint suffix is removed so it doesn't persist into the next streaming segment. 3. When final _stream_end closes the card, tool hint suffix is cleaned from the text before the final card update. Adds 3 regression tests covering all three scenarios. Made-with: Cursor
This commit is contained in:
parent
8d6f41e484
commit
a4bb1923ac
@ -1287,6 +1287,9 @@ 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:
|
||||
if buf.tool_hint_len > 0:
|
||||
buf.text = buf.text[:-buf.tool_hint_len]
|
||||
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,
|
||||
@ -1296,6 +1299,9 @@ class FeishuChannel(BaseChannel):
|
||||
buf = self._stream_bufs.pop(chat_id, None)
|
||||
if not buf or not buf.text:
|
||||
return
|
||||
if buf.tool_hint_len > 0:
|
||||
buf.text = buf.text[:-buf.tool_hint_len]
|
||||
buf.tool_hint_len = 0
|
||||
if buf.card_id:
|
||||
buf.sequence += 1
|
||||
await loop.run_in_executor(
|
||||
@ -1376,6 +1382,8 @@ 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]
|
||||
suffix = f"\n\n---\n🔧 {hint}"
|
||||
buf.text += suffix
|
||||
buf.tool_hint_len = len(suffix)
|
||||
|
||||
@ -349,6 +349,70 @@ class TestToolHintInlineStreaming:
|
||||
assert "oc_chat1" not in ch._stream_bufs
|
||||
ch._client.im.v1.message.create.assert_called_once()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_consecutive_tool_hints_replace_previous(self):
|
||||
"""When multiple tool hints arrive consecutively, each replaces the previous one."""
|
||||
ch = _make_channel()
|
||||
ch._stream_bufs["oc_chat1"] = _FeishuStreamBuf(
|
||||
text="Partial answer", card_id="card_1", sequence=2, last_edit=0.0,
|
||||
)
|
||||
ch._client.cardkit.v1.card_element.content.return_value = _mock_content_response()
|
||||
|
||||
msg1 = OutboundMessage(
|
||||
channel="feishu", chat_id="oc_chat1",
|
||||
content='$ cd /project', metadata={"_tool_hint": True},
|
||||
)
|
||||
await ch.send(msg1)
|
||||
|
||||
msg2 = OutboundMessage(
|
||||
channel="feishu", chat_id="oc_chat1",
|
||||
content='$ git status', metadata={"_tool_hint": True},
|
||||
)
|
||||
await ch.send(msg2)
|
||||
|
||||
buf = ch._stream_bufs["oc_chat1"]
|
||||
assert buf.text.count("$ cd /project") == 0
|
||||
assert buf.text.count("$ git status") == 1
|
||||
assert buf.text.startswith("Partial answer")
|
||||
assert buf.text.endswith("🔧 $ git status")
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_tool_hint_stripped_on_resuming_flush(self):
|
||||
"""When _resuming flushes the buffer, tool hint suffix is cleaned."""
|
||||
ch = _make_channel()
|
||||
suffix = "\n\n---\n🔧 $ cd /project"
|
||||
ch._stream_bufs["oc_chat1"] = _FeishuStreamBuf(
|
||||
text="Partial answer" + suffix,
|
||||
card_id="card_1", sequence=2, last_edit=0.0,
|
||||
tool_hint_len=len(suffix),
|
||||
)
|
||||
ch._client.cardkit.v1.card_element.content.return_value = _mock_content_response()
|
||||
|
||||
await ch.send_delta("oc_chat1", "", metadata={"_stream_end": True, "_resuming": True})
|
||||
|
||||
buf = ch._stream_bufs["oc_chat1"]
|
||||
assert buf.text == "Partial answer"
|
||||
assert buf.tool_hint_len == 0
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_tool_hint_stripped_on_final_stream_end(self):
|
||||
"""When final _stream_end closes the card, tool hint suffix is cleaned from text."""
|
||||
ch = _make_channel()
|
||||
suffix = "\n\n---\n🔧 web_fetch(\"url\")"
|
||||
ch._stream_bufs["oc_chat1"] = _FeishuStreamBuf(
|
||||
text="Final content" + suffix,
|
||||
card_id="card_1", sequence=3, last_edit=0.0,
|
||||
tool_hint_len=len(suffix),
|
||||
)
|
||||
ch._client.cardkit.v1.card_element.content.return_value = _mock_content_response()
|
||||
ch._client.cardkit.v1.card.settings.return_value = _mock_content_response()
|
||||
|
||||
await ch.send_delta("oc_chat1", "", metadata={"_stream_end": True})
|
||||
|
||||
assert "oc_chat1" not in ch._stream_bufs
|
||||
update_call = ch._client.cardkit.v1.card_element.content.call_args[0][0]
|
||||
assert "🔧" not in update_call.body.content
|
||||
|
||||
|
||||
class TestSendMessageReturnsId:
|
||||
def test_returns_message_id_on_success(self):
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user