Files
wehub-resource-sync bcbd1bdb22
Integ / changes (push) Has been skipped
Pre-commit / pre-commit (push) Failing after 1s
CLI exit codes / changes (push) Has been skipped
Test (Install) / changes (push) Has been skipped
Test (Python) / changes (push) Has been skipped
Test (TypeScript) / changes (push) Has been skipped
CLI exit codes / cli-gate (push) Has been cancelled
Test (Install) / test-install-gate (push) Has been cancelled
Integ / integ-gate (push) Has been cancelled
Test (Python) / test-python-gate (push) Has been cancelled
Test (TypeScript) / test-typescript-gate (push) Has been cancelled
Test (Install) / python-minimal (3.12) (push) Has been cancelled
Test (Install) / python-minimal (3.11) (push) Has been cancelled
Test (Install) / python-extra (agno, mirage.agents.agno) (push) Has been cancelled
Test (Install) / python-extra (chroma, mirage.resource.chroma) (push) Has been cancelled
Test (Install) / python-extra (pdf, mirage.core.filetype.pdf) (push) Has been cancelled
Integ / integ (push) Has been cancelled
Integ / integ-database (push) Has been cancelled
Integ / integ-database-ts (push) Has been cancelled
Integ / integ-data (push) Has been cancelled
Integ / integ-ssh (push) Has been cancelled
Integ / integ-ssh-ts (push) Has been cancelled
Test (Python) / audit (push) Has been cancelled
Test (TypeScript) / test (push) Has been cancelled
Test (TypeScript) / python-fs-shim (push) Has been cancelled
CLI exit codes / Python CLI (push) Has been cancelled
CLI exit codes / TypeScript CLI (push) Has been cancelled
CLI exit codes / Cross-language snapshot interop (push) Has been cancelled
Test (Python) / test (push) Has been cancelled
Test (Python) / import-isolation (deepagents, openai, mirage.agents.openai_agents) (push) Has been cancelled
Test (Python) / import-isolation (deepagents, pydantic-ai, mirage.agents.pydantic_ai) (push) Has been cancelled
Integ / integ-ts (push) Has been cancelled
Integ / integ-fuse (push) Has been cancelled
Test (Install) / python-extra (databricks, mirage.resource.databricks_volume) (push) Has been cancelled
Test (Install) / python-extra (deepagents, mirage.agents.langchain) (push) Has been cancelled
Test (Install) / python-extra (email, mirage.resource.email) (push) Has been cancelled
Test (Install) / python-extra (fuse, mirage.fuse.mount) (push) Has been cancelled
Test (Install) / python-extra (hdf5, mirage.core.filetype.hdf5) (push) Has been cancelled
Test (Install) / python-extra (hf, mirage.resource.hf_buckets) (push) Has been cancelled
Test (Install) / python-extra (lancedb, mirage.resource.lancedb) (push) Has been cancelled
Test (Install) / python-extra (langfuse, mirage.resource.langfuse) (push) Has been cancelled
Test (Install) / python-extra (mongodb, mirage.resource.mongodb) (push) Has been cancelled
Test (Install) / python-extra (nextcloud, mirage.resource.nextcloud) (push) Has been cancelled
Test (Install) / python-extra (openai, mirage.agents.openai_agents) (push) Has been cancelled
Test (Install) / python-extra (openhands, mirage.agents.openhands, 3.12) (push) Has been cancelled
Test (Install) / python-extra (parquet, mirage.core.filetype.parquet) (push) Has been cancelled
Test (Install) / python-extra (postgres, mirage.resource.postgres) (push) Has been cancelled
Test (Install) / python-extra (pydantic-ai, mirage.agents.pydantic_ai) (push) Has been cancelled
Test (Install) / python-extra (qdrant, mirage.resource.qdrant) (push) Has been cancelled
Test (Install) / python-extra (redis, mirage.resource.redis) (push) Has been cancelled
Test (Install) / python-extra (s3, mirage.resource.s3) (push) Has been cancelled
Test (Install) / python-extra (ssh, mirage.resource.ssh) (push) Has been cancelled
Test (Install) / ts-minimal (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:30:44 +08:00

158 lines
5.2 KiB
Python

# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
import base64
from email.mime.text import MIMEText
from mirage.core.gmail.messages import (_extract_header, get_message_processed,
get_message_raw)
from mirage.core.google._client import (GMAIL_API_BASE, TokenManager,
google_post)
async def send_message(
token_manager: TokenManager,
to: str,
subject: str,
body: str,
) -> dict:
"""Send a new email.
Args:
token_manager (TokenManager): manages OAuth2 tokens.
to (str): recipient email address.
subject (str): email subject.
body (str): plain-text email body.
Returns:
dict: API response with sent message metadata.
"""
msg = MIMEText(body)
msg["To"] = to
msg["Subject"] = subject
raw = base64.urlsafe_b64encode(msg.as_bytes()).decode()
url = f"{GMAIL_API_BASE}/users/me/messages/send"
return await google_post(token_manager, url, {"raw": raw})
async def reply_message(
token_manager: TokenManager,
message_id: str,
body: str,
) -> dict:
"""Reply to a message (preserves threading).
Args:
token_manager (TokenManager): manages OAuth2 tokens.
message_id (str): ID of the message to reply to.
body (str): plain-text reply body.
Returns:
dict: API response with sent message metadata.
"""
raw_msg = await get_message_raw(token_manager, message_id)
headers = raw_msg.get("payload", {}).get("headers", [])
to = _extract_header(headers, "From")
subject = _extract_header(headers, "Subject")
if not subject.lower().startswith("re:"):
subject = f"Re: {subject}"
thread_id = raw_msg.get("threadId", "")
msg_id_header = _extract_header(headers, "Message-ID")
mime = MIMEText(body)
mime["To"] = to
mime["Subject"] = subject
if msg_id_header:
mime["In-Reply-To"] = msg_id_header
mime["References"] = msg_id_header
raw = base64.urlsafe_b64encode(mime.as_bytes()).decode()
payload: dict = {"raw": raw}
if thread_id:
payload["threadId"] = thread_id
url = f"{GMAIL_API_BASE}/users/me/messages/send"
return await google_post(token_manager, url, payload)
async def reply_all_message(
token_manager: TokenManager,
message_id: str,
body: str,
) -> dict:
"""Reply-all to a message (preserves threading, includes all recipients).
Args:
token_manager (TokenManager): manages OAuth2 tokens.
message_id (str): ID of the message to reply to.
body (str): plain-text reply body.
Returns:
dict: API response with sent message metadata.
"""
raw_msg = await get_message_raw(token_manager, message_id)
headers = raw_msg.get("payload", {}).get("headers", [])
sender = _extract_header(headers, "From")
original_to = _extract_header(headers, "To")
original_cc = _extract_header(headers, "Cc")
subject = _extract_header(headers, "Subject")
if not subject.lower().startswith("re:"):
subject = f"Re: {subject}"
thread_id = raw_msg.get("threadId", "")
msg_id_header = _extract_header(headers, "Message-ID")
all_to = ", ".join(filter(None, [sender, original_to]))
mime = MIMEText(body)
mime["To"] = all_to
if original_cc:
mime["Cc"] = original_cc
mime["Subject"] = subject
if msg_id_header:
mime["In-Reply-To"] = msg_id_header
mime["References"] = msg_id_header
raw = base64.urlsafe_b64encode(mime.as_bytes()).decode()
payload: dict = {"raw": raw}
if thread_id:
payload["threadId"] = thread_id
url = f"{GMAIL_API_BASE}/users/me/messages/send"
return await google_post(token_manager, url, payload)
async def forward_message(
token_manager: TokenManager,
message_id: str,
to: str,
) -> dict:
"""Forward a message.
Args:
token_manager (TokenManager): manages OAuth2 tokens.
message_id (str): ID of the message to forward.
to (str): recipient email address.
Returns:
dict: API response with sent message metadata.
"""
processed = await get_message_processed(token_manager, message_id)
subject = processed.get("subject", "")
if not subject.lower().startswith("fwd:"):
subject = f"Fwd: {subject}"
fwd_body = (f"---------- Forwarded message ----------\n"
f"From: {processed['from'].get('email', '')}\n"
f"Date: {processed.get('date', '')}\n"
f"Subject: {processed.get('subject', '')}\n\n"
f"{processed.get('body_text', '')}")
return await send_message(token_manager, to, subject, fwd_body)