Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 24 additions & 17 deletions plugins/platforms/feishu/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,12 +152,11 @@
# ---------------------------------------------------------------------------

_MARKDOWN_HINT_RE = re.compile(
r"(^#{1,6}\s)|(^\s*[-*]\s)|(^\s*\d+\.\s)|(^\s*---+\s*$)|(```)|(`[^`\n]+`)|(\*\*[^*\n].+?\*\*)|(~~[^~\n].+?~~)|(<u>.+?</u>)|(\*[^*\n]+\*)|(\[[^\]]+\]\([^)]+\))|(^>\s)",
r"(^#{1,6}\s)|(^\s*[-*]\s)|(^\s*\|)|(^\s*\d+\.\s)|(^\s*---+\s*$)|"
r"(```)|(`[^`\n]+`)|(\*\*[^*\n].+?\*\*)|(~~[^~\n].+?~~)|(<u>.+?</u>)|"
r"(\*[^*\n]+\*)|(\[[^\]]+\]\([^)]+\))|(^>\s)",
re.MULTILINE,
)
# Detect markdown tables: a line starting with | followed by a separator line.
# Feishu post-type 'md' elements do not render tables, so we force text mode.
_MARKDOWN_TABLE_RE = re.compile(r"^\|.*\|\n\|[-|: ]+\|", re.MULTILINE)
_MARKDOWN_LINK_RE = re.compile(r"\[([^\]]+)\]\(([^)]+)\)")
_MARKDOWN_FENCE_OPEN_RE = re.compile(r"^```([^\n`]*)\s*$")
_MARKDOWN_FENCE_CLOSE_RE = re.compile(r"^```\s*$")
Expand Down Expand Up @@ -1913,11 +1912,11 @@ async def send(
except Exception as exc:
if msg_type != "post" or not _POST_CONTENT_INVALID_RE.search(str(exc)):
raise
logger.warning("[Feishu] Invalid post payload rejected by API; falling back to plain text")
logger.warning("[Feishu] Invalid post payload rejected by API; retrying as post")
response = await self._feishu_send_with_retry(
chat_id=chat_id,
msg_type="text",
payload=json.dumps({"text": _strip_markdown_to_plain_text(chunk)}, ensure_ascii=False),
msg_type="post",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Feishu has already rejected this exact post payload as invalid, and this retry resubmits it unchanged. That removes the current post→text delivery fallback for unsupported Markdown; preserve the text fallback here and in the response-error branch, while routing valid tables to post on the normal path.

payload=payload,
reply_to=reply_to,
metadata=metadata,
)
Expand All @@ -1926,11 +1925,11 @@ async def send(
and not self._response_succeeded(response)
and _POST_CONTENT_INVALID_RE.search(str(getattr(response, "msg", "") or ""))
):
logger.warning("[Feishu] Post payload rejected by API response; falling back to plain text")
logger.warning("[Feishu] Post payload rejected by API response; retrying as post")
response = await self._feishu_send_with_retry(
chat_id=chat_id,
msg_type="text",
payload=json.dumps({"text": _strip_markdown_to_plain_text(chunk)}, ensure_ascii=False),
msg_type="post",
payload=payload,
reply_to=reply_to,
metadata=metadata,
)
Expand Down Expand Up @@ -1961,7 +1960,19 @@ async def edit_message(
response = await self._run_blocking(self._client.im.v1.message.update, request)
result = self._finalize_send_result(response, "update failed")
if not result.success and msg_type == "post" and _POST_CONTENT_INVALID_RE.search(result.error or ""):
logger.warning("[Feishu] Invalid post update payload rejected by API; falling back to plain text")
logger.warning("[Feishu] Invalid post update rejected by API; falling back to send-as-continuation")
# Instead of stripping to plain text (which destroys table formatting),
# send the content as a NEW post message so the table renders correctly.
# The stream consumer's on_new_message callback will thread subsequent
# chunks below this new bubble, and the old malformed message stays as-is.
fallback_result = await self.send(
chat_id=chat_id,
content=content,
metadata={"hermes_stream_fallback": True, "reply_to": message_id},
)
if fallback_result.success:
return fallback_result
# If even send fails, last-resort: plain text (but this is rare)
fallback_body = self._build_update_message_body(
msg_type="text",
content=json.dumps({"text": _strip_markdown_to_plain_text(content)}, ensure_ascii=False),
Expand Down Expand Up @@ -4522,12 +4533,8 @@ def _is_duplicate(self, message_id: str) -> bool:
# =========================================================================

def _build_outbound_payload(self, content: str) -> tuple[str, str]:
# Feishu post-type 'md' elements do not render markdown tables; sending
# table content as post causes the message to appear blank on the client.
# Force plain text for anything that looks like a markdown table.
if _MARKDOWN_TABLE_RE.search(content):
text_payload = {"text": content}
return "text", json.dumps(text_payload, ensure_ascii=False)
# Feishu post-type 'md' elements DO support GFM table syntax.
# Table content should be routed to 'post' type for native rendering.
if _MARKDOWN_HINT_RE.search(content):
return "post", _build_markdown_post_payload(content)
text_payload = {"text": content}
Expand Down
Loading