feat(feishu): display tool calls in code block messages
- Tool hint messages with _tool_hint metadata now render as formatted code blocks - Uses Feishu interactive card message type with markdown code fences - Shows "Tool Call" header followed by code in a monospace block - Adds comprehensive unit tests for the new functionality Co-Authorship-Bot: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -822,18 +822,24 @@ class FeishuChannel(BaseChannel):
|
|||||||
receive_id_type = "chat_id" if msg.chat_id.startswith("oc_") else "open_id"
|
receive_id_type = "chat_id" if msg.chat_id.startswith("oc_") else "open_id"
|
||||||
loop = asyncio.get_running_loop()
|
loop = asyncio.get_running_loop()
|
||||||
|
|
||||||
# Handle tool hint messages as code blocks
|
# Handle tool hint messages as code blocks in interactive cards
|
||||||
if msg.metadata.get("_tool_hint"):
|
if msg.metadata.get("_tool_hint"):
|
||||||
if msg.content and msg.content.strip():
|
if msg.content and msg.content.strip():
|
||||||
code_content = {
|
# Create a simple card with a code block
|
||||||
"title": "Tool Call",
|
code_text = msg.content.strip()
|
||||||
"code": msg.content.strip(),
|
card = {
|
||||||
"language": "text"
|
"config": {"wide_screen_mode": True},
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"tag": "markdown",
|
||||||
|
"content": f"**Tool Call**\n\n```\n{code_text}\n```"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
await loop.run_in_executor(
|
await loop.run_in_executor(
|
||||||
None, self._send_message_sync,
|
None, self._send_message_sync,
|
||||||
receive_id_type, msg.chat_id, "code",
|
receive_id_type, msg.chat_id, "interactive",
|
||||||
json.dumps(code_content, ensure_ascii=False),
|
json.dumps(card, ensure_ascii=False),
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ def mock_feishu_channel():
|
|||||||
|
|
||||||
|
|
||||||
def test_tool_hint_sends_code_message(mock_feishu_channel):
|
def test_tool_hint_sends_code_message(mock_feishu_channel):
|
||||||
"""Tool hint messages should be sent as code blocks."""
|
"""Tool hint messages should be sent as interactive cards with code blocks."""
|
||||||
msg = OutboundMessage(
|
msg = OutboundMessage(
|
||||||
channel="feishu",
|
channel="feishu",
|
||||||
chat_id="oc_123456",
|
chat_id="oc_123456",
|
||||||
@@ -37,20 +37,23 @@ def test_tool_hint_sends_code_message(mock_feishu_channel):
|
|||||||
import asyncio
|
import asyncio
|
||||||
asyncio.run(mock_feishu_channel.send(msg))
|
asyncio.run(mock_feishu_channel.send(msg))
|
||||||
|
|
||||||
# Verify code message was sent
|
# Verify interactive message with card was sent
|
||||||
assert mock_send.call_count == 1
|
assert mock_send.call_count == 1
|
||||||
call_args = mock_send.call_args[0]
|
call_args = mock_send.call_args[0]
|
||||||
receive_id_type, receive_id, msg_type, content = call_args
|
receive_id_type, receive_id, msg_type, content = call_args
|
||||||
|
|
||||||
assert receive_id_type == "chat_id"
|
assert receive_id_type == "chat_id"
|
||||||
assert receive_id == "oc_123456"
|
assert receive_id == "oc_123456"
|
||||||
assert msg_type == "code"
|
assert msg_type == "interactive"
|
||||||
|
|
||||||
# Parse content to verify structure
|
# Parse content to verify card structure
|
||||||
content_dict = json.loads(content)
|
card = json.loads(content)
|
||||||
assert content_dict["title"] == "Tool Call"
|
assert card["config"]["wide_screen_mode"] is True
|
||||||
assert content_dict["code"] == 'web_search("test query")'
|
assert len(card["elements"]) == 1
|
||||||
assert content_dict["language"] == "text"
|
assert card["elements"][0]["tag"] == "markdown"
|
||||||
|
# Check that code block is properly formatted
|
||||||
|
expected_md = "**Tool Call**\n\n```\nweb_search(\"test query\")\n```"
|
||||||
|
assert card["elements"][0]["content"] == expected_md
|
||||||
|
|
||||||
|
|
||||||
def test_tool_hint_empty_content_does_not_send(mock_feishu_channel):
|
def test_tool_hint_empty_content_does_not_send(mock_feishu_channel):
|
||||||
@@ -105,6 +108,7 @@ def test_tool_hint_multiple_tools_in_one_message(mock_feishu_channel):
|
|||||||
asyncio.run(mock_feishu_channel.send(msg))
|
asyncio.run(mock_feishu_channel.send(msg))
|
||||||
|
|
||||||
call_args = mock_send.call_args[0]
|
call_args = mock_send.call_args[0]
|
||||||
|
msg_type = call_args[2]
|
||||||
content = json.loads(call_args[3])
|
content = json.loads(call_args[3])
|
||||||
assert content["code"] == 'web_search("query"), read_file("/path/to/file")'
|
assert msg_type == "interactive"
|
||||||
assert "\n" not in content["code"] # Single line as intended
|
assert "web_search(\"query\"), read_file(\"/path/to/file\")" in content["elements"][0]["content"]
|
||||||
|
|||||||
Reference in New Issue
Block a user