refac
This commit is contained in:
@@ -7,6 +7,7 @@ This module provides MCP tools for interacting with the Gmail API.
|
|||||||
import logging
|
import logging
|
||||||
import asyncio
|
import asyncio
|
||||||
import base64
|
import base64
|
||||||
|
import re
|
||||||
import ssl
|
import ssl
|
||||||
import mimetypes
|
import mimetypes
|
||||||
from html.parser import HTMLParser
|
from html.parser import HTMLParser
|
||||||
@@ -441,33 +442,74 @@ def _extract_headers(payload: dict, header_names: List[str]) -> Dict[str, str]:
|
|||||||
return headers
|
return headers
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_message_id_chain(header_value: Optional[str]) -> List[str]:
|
||||||
|
"""Extract Message-IDs from a reply header value."""
|
||||||
|
if not header_value:
|
||||||
|
return []
|
||||||
|
|
||||||
async def _fetch_thread_message_ids(service, thread_id: str) -> tuple[Optional[str], Optional[str]]:
|
message_ids = re.findall(r"<[^>]+>", header_value)
|
||||||
|
if message_ids:
|
||||||
|
return message_ids
|
||||||
|
|
||||||
|
return header_value.split()
|
||||||
|
|
||||||
|
|
||||||
|
def _derive_reply_headers(
|
||||||
|
thread_message_ids: List[str],
|
||||||
|
in_reply_to: Optional[str],
|
||||||
|
references: Optional[str],
|
||||||
|
) -> tuple[Optional[str], Optional[str]]:
|
||||||
|
"""Fill missing reply headers while preserving caller intent."""
|
||||||
|
derived_in_reply_to = in_reply_to
|
||||||
|
derived_references = references
|
||||||
|
|
||||||
|
if not thread_message_ids:
|
||||||
|
return derived_in_reply_to, derived_references
|
||||||
|
|
||||||
|
if not derived_in_reply_to:
|
||||||
|
reference_chain = _parse_message_id_chain(derived_references)
|
||||||
|
derived_in_reply_to = (
|
||||||
|
reference_chain[-1] if reference_chain else thread_message_ids[-1]
|
||||||
|
)
|
||||||
|
|
||||||
|
if not derived_references:
|
||||||
|
if derived_in_reply_to and derived_in_reply_to in thread_message_ids:
|
||||||
|
reply_index = thread_message_ids.index(derived_in_reply_to)
|
||||||
|
derived_references = " ".join(thread_message_ids[: reply_index + 1])
|
||||||
|
elif derived_in_reply_to:
|
||||||
|
derived_references = derived_in_reply_to
|
||||||
|
else:
|
||||||
|
derived_references = " ".join(thread_message_ids)
|
||||||
|
|
||||||
|
return derived_in_reply_to, derived_references
|
||||||
|
|
||||||
|
|
||||||
|
async def _fetch_thread_message_ids(service, thread_id: str) -> List[str]:
|
||||||
"""
|
"""
|
||||||
Fetch Message-ID headers from a Gmail thread for reply threading.
|
Fetch Message-ID headers from a Gmail thread for reply threading.
|
||||||
|
|
||||||
Returns the last message's Message-ID (for In-Reply-To) and the full chain
|
|
||||||
of Message-IDs (for References).
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
service: Gmail API service instance
|
service: Gmail API service instance
|
||||||
thread_id: Gmail thread ID
|
thread_id: Gmail thread ID
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Tuple of (last_message_id, references_chain) where both are strings or None
|
Message-IDs in thread order. Returns an empty list on failure.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
thread = await asyncio.to_thread(
|
thread = await asyncio.to_thread(
|
||||||
service.users().threads().get(
|
service.users()
|
||||||
|
.threads()
|
||||||
|
.get(
|
||||||
userId="me",
|
userId="me",
|
||||||
id=thread_id,
|
id=thread_id,
|
||||||
format="metadata",
|
format="metadata",
|
||||||
metadataHeaders=["Message-ID"],
|
metadataHeaders=["Message-ID"],
|
||||||
).execute
|
)
|
||||||
|
.execute
|
||||||
)
|
)
|
||||||
messages = thread.get("messages", [])
|
messages = thread.get("messages", [])
|
||||||
if not messages:
|
if not messages:
|
||||||
return None, None
|
return []
|
||||||
|
|
||||||
# Collect all Message-IDs in thread order
|
# Collect all Message-IDs in thread order
|
||||||
message_ids = []
|
message_ids = []
|
||||||
@@ -477,15 +519,12 @@ async def _fetch_thread_message_ids(service, thread_id: str) -> tuple[Optional[s
|
|||||||
if mid:
|
if mid:
|
||||||
message_ids.append(mid)
|
message_ids.append(mid)
|
||||||
|
|
||||||
if not message_ids:
|
return message_ids
|
||||||
return None, None
|
|
||||||
|
|
||||||
last_message_id = message_ids[-1]
|
|
||||||
references_chain = " ".join(message_ids)
|
|
||||||
return last_message_id, references_chain
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Failed to fetch thread Message-IDs for thread {thread_id}: {e}")
|
logger.warning(
|
||||||
return None, None
|
f"Failed to fetch thread Message-IDs for thread {thread_id}: {e}"
|
||||||
|
)
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
def _prepare_gmail_message(
|
def _prepare_gmail_message(
|
||||||
@@ -1695,13 +1734,10 @@ async def draft_gmail_message(
|
|||||||
# Auto-populate In-Reply-To and References when thread_id is provided
|
# Auto-populate In-Reply-To and References when thread_id is provided
|
||||||
# but headers are missing, to ensure the draft renders inline in Gmail
|
# but headers are missing, to ensure the draft renders inline in Gmail
|
||||||
if thread_id and (not in_reply_to or not references):
|
if thread_id and (not in_reply_to or not references):
|
||||||
fetched_reply_to, fetched_references = await _fetch_thread_message_ids(
|
thread_message_ids = await _fetch_thread_message_ids(service, thread_id)
|
||||||
service, thread_id
|
in_reply_to, references = _derive_reply_headers(
|
||||||
|
thread_message_ids, in_reply_to, references
|
||||||
)
|
)
|
||||||
if not in_reply_to and fetched_reply_to:
|
|
||||||
in_reply_to = fetched_reply_to
|
|
||||||
if not references and fetched_references:
|
|
||||||
references = fetched_references
|
|
||||||
|
|
||||||
raw_message, thread_id_final, attached_count = _prepare_gmail_message(
|
raw_message, thread_id_final, attached_count = _prepare_gmail_message(
|
||||||
subject=subject,
|
subject=subject,
|
||||||
|
|||||||
Reference in New Issue
Block a user