import asyncio from typing import Any from googleapiclient.discovery import build from app.google.auth import GoogleAuthError, GoogleAuthManager from app.tools.base import Tool class GmailTool(Tool): name = "gmail" description = "List and search Gmail messages for the connected Google account." def __init__(self, auth_manager: GoogleAuthManager) -> None: self.auth_manager = auth_manager def parameters_schema(self) -> dict[str, Any]: return { "type": "object", "properties": { "query": { "type": "string", "description": "Optional Gmail search query such as from:someone newer_than:7d.", }, "max_results": { "type": "integer", "description": "Maximum number of messages to return, from 1 to 20.", "minimum": 1, "maximum": 20, }, "label_ids": { "type": "array", "items": {"type": "string"}, "description": "Optional Gmail label filters. Defaults to INBOX.", }, }, "additionalProperties": False, } async def run(self, payload: dict[str, Any]) -> dict[str, Any]: query = str(payload.get("query", "")).strip() max_results = max(1, min(20, int(payload.get("max_results", 10) or 10))) label_ids = payload.get("label_ids") if not isinstance(label_ids, list) or not label_ids: label_ids = ["INBOX"] try: creds = await self.auth_manager.get_credentials() except GoogleAuthError as exc: return {"tool": self.name, "status": "error", "message": str(exc)} return await asyncio.to_thread( self._list_messages, creds, query, max_results, [str(label) for label in label_ids], ) def _list_messages( self, credentials: Any, query: str, max_results: int, label_ids: list[str], ) -> dict[str, Any]: service = build("gmail", "v1", credentials=credentials, cache_discovery=False) response = ( service.users() .messages() .list(userId="me", q=query or None, labelIds=label_ids, maxResults=max_results) .execute() ) message_refs = response.get("messages", []) messages = [] for item in message_refs: detail = ( service.users() .messages() .get( userId="me", id=item["id"], format="metadata", metadataHeaders=["From", "Subject", "Date"], ) .execute() ) headers = { header.get("name", "").lower(): header.get("value", "") for header in detail.get("payload", {}).get("headers", []) } messages.append( { "id": detail.get("id", ""), "thread_id": detail.get("threadId", ""), "from": headers.get("from", ""), "subject": headers.get("subject", "(no subject)"), "date": headers.get("date", ""), "snippet": detail.get("snippet", ""), "label_ids": detail.get("labelIds", []), } ) return { "tool": self.name, "status": "ok", "query": query, "count": len(messages), "messages": messages, }