Add iMessage threading, typing indicators, and fix tool-use response extraction
- Native reply threading: inbound replies continue their conversation via SendBlue message_handle lookup; standalone messages create a new conversation - Our responses are sent with reply_to so they thread visually in iMessage - Typing indicator fires before agent invocation and stops in a finally block - Add extract_agent_text() to handle list-type AIMessage content and strip <think> tokens from Qwen3 — applied to iMessage, WhatsApp, and web chat - ConversationMessage gains a nullable message_handle field (migration needed) - /threads command lists recent iMessage conversations Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,26 @@
|
||||
import re
|
||||
|
||||
_THINK_RE = re.compile(r"<think>.*?</think>", re.DOTALL)
|
||||
|
||||
|
||||
def extract_agent_text(content) -> str:
|
||||
"""Extract plain text from a LangChain AIMessage content.
|
||||
|
||||
Handles both str and list-of-blocks content (the latter occurs with some
|
||||
tool-using or vision models), and strips <think>...</think> tokens emitted
|
||||
by reasoning models like Qwen3.
|
||||
"""
|
||||
if isinstance(content, list):
|
||||
text = "".join(
|
||||
block.get("text", "")
|
||||
if isinstance(block, dict) and block.get("type") != "thinking"
|
||||
else (str(block) if not isinstance(block, dict) else "")
|
||||
for block in content
|
||||
)
|
||||
else:
|
||||
text = str(content) if content is not None else ""
|
||||
return _THINK_RE.sub("", text).strip()
|
||||
|
||||
|
||||
def strip_markdown(text: str) -> str:
|
||||
"""Strip markdown formatting from text for plain-text channels like iMessage."""
|
||||
|
||||
Reference in New Issue
Block a user