UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

159 lines • 6.14 kB
// ========= 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 { decodeBase64 } from "../../utils/base64.js"; import { gmailBase, googleGet } from "../google/client.js"; import { compactJsonBytes } from "../render/json.js"; /** * One page of message stubs for a label or query, and the next page's token, * null on the last page. Gmail may return fewer than `maxResults` stubs and * still name a next page. Mirrors Python's `list_message_page`. */ export async function listMessagePage(tokenManager, opts = {}) { const params = { maxResults: opts.maxResults ?? 50 }; if (opts.labelId !== undefined && opts.labelId !== null && opts.labelId !== '') { params.labelIds = opts.labelId; } if (opts.query !== undefined && opts.query !== null && opts.query !== '') { params.q = opts.query; } const url = `${gmailBase(tokenManager)}/users/me/messages`; const data = (await googleGet(tokenManager, url, params)); return [data.messages ?? [], data.nextPageToken ?? null]; } export async function listMessages(tokenManager, opts = {}) { const [stubs] = await listMessagePage(tokenManager, opts); return stubs; } export async function getMessageRaw(tokenManager, messageId) { const url = `${gmailBase(tokenManager)}/users/me/messages/${messageId}?format=full`; return (await googleGet(tokenManager, url)); } function base64UrlDecodeToBytes(input) { const padded = input + '=='.slice((input.length + 2) % 4); return decodeBase64(padded.replace(/-/g, '+').replace(/_/g, '/')); } const TEXT_DECODER = new TextDecoder('utf-8', { fatal: false }); export function decodeBody(payload) { if (payload === undefined) return ''; if (payload.mimeType === 'text/plain') { const data = payload.body?.data ?? ''; if (data !== '') { return TEXT_DECODER.decode(base64UrlDecodeToBytes(data)); } } for (const part of payload.parts ?? []) { const text = decodeBody(part); if (text !== '') return text; } return ''; } export function extractHeader(headers, name) { if (headers === undefined) return ''; const lower = name.toLowerCase(); for (const h of headers) { if ((h.name ?? '').toLowerCase() === lower) return h.value ?? ''; } return ''; } export function parseAddress(raw) { if (raw.includes('<') && raw.includes('>')) { const lt = raw.indexOf('<'); const gt = raw.indexOf('>'); const name = raw.slice(0, lt).trim().replace(/^"|"$/g, ''); const email = raw.slice(lt + 1, gt).trim(); return { name, email }; } return { name: '', email: raw.trim() }; } function parseAddressList(raw) { if (raw === '') return []; return raw.split(',').map((a) => parseAddress(a.trim())); } export async function getAttachment(tokenManager, messageId, attachmentId) { const url = `${gmailBase(tokenManager)}/users/me/messages/${messageId}/attachments/${attachmentId}`; const data = (await googleGet(tokenManager, url)); const raw = data.data ?? ''; return base64UrlDecodeToBytes(raw); } export function extractAttachments(payload) { if (payload === undefined) return []; const attachments = []; const parts = payload.parts ?? []; for (const part of parts) { const filename = part.filename ?? ''; const body = part.body ?? {}; const attachmentId = body.attachmentId ?? ''; if (filename !== '' && attachmentId !== '') { attachments.push({ filename, attachmentId, size: body.size ?? 0, mimeType: part.mimeType ?? '', }); } for (const sub of part.parts ?? []) { const fn = sub.filename ?? ''; const bd = sub.body ?? {}; const aid = bd.attachmentId ?? ''; if (fn !== '' && aid !== '') { attachments.push({ filename: fn, attachmentId: aid, size: bd.size ?? 0, mimeType: sub.mimeType ?? '', }); } } } return attachments; } export async function getMessageProcessed(tokenManager, messageId) { const raw = await getMessageRaw(tokenManager, messageId); return processMessage(raw); } export function processMessage(raw) { const headers = raw.payload?.headers ?? []; const bodyText = decodeBody(raw.payload); return { id: raw.id ?? '', thread_id: raw.threadId ?? '', from: parseAddress(extractHeader(headers, 'From')), to: parseAddressList(extractHeader(headers, 'To')), cc: parseAddressList(extractHeader(headers, 'Cc')), subject: extractHeader(headers, 'Subject'), date: extractHeader(headers, 'Date'), body_text: bodyText, snippet: raw.snippet ?? '', labels: raw.labelIds ?? [], attachments: extractAttachments(raw.payload).map((a) => ({ id: a.attachmentId, filename: a.filename, path: `attachments/${a.attachmentId}_${a.filename}`, mime_type: a.mimeType, size: a.size, })), }; } // The single renderer behind both read and the readdir-time size, so // stat().size == len(read()) by construction. export function messageJsonBytes(raw) { return compactJsonBytes(processMessage(raw)); } //# sourceMappingURL=messages.js.map