Consolidate
This commit is contained in:
parent
4dbd7ab0ce
commit
258a4bbe49
@ -1,11 +1,6 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
listAgentMailAccountIds,
|
||||
resolveAgentMailAccount,
|
||||
resolveCredentials,
|
||||
resolveDefaultAgentMailAccountId,
|
||||
} from "./accounts.js";
|
||||
import { resolveAgentMailAccount, resolveCredentials } from "./accounts.js";
|
||||
import type { CoreConfig } from "./utils.js";
|
||||
|
||||
describe("resolveCredentials", () => {
|
||||
@ -99,7 +94,9 @@ describe("resolveCredentials", () => {
|
||||
AGENTMAIL_WEBHOOK_URL: "https://env-gateway.example.com/hooks/email",
|
||||
};
|
||||
const result = resolveCredentials(cfg, env);
|
||||
expect(result.webhookUrl).toBe("https://env-gateway.example.com/hooks/email");
|
||||
expect(result.webhookUrl).toBe(
|
||||
"https://env-gateway.example.com/hooks/email"
|
||||
);
|
||||
});
|
||||
|
||||
it("derives webhookPath from webhookUrl", () => {
|
||||
@ -218,19 +215,3 @@ describe("resolveAgentMailAccount", () => {
|
||||
expect(result.name).toBe("Trimmed Name");
|
||||
});
|
||||
});
|
||||
|
||||
describe("listAgentMailAccountIds", () => {
|
||||
it("returns default account", () => {
|
||||
const cfg: CoreConfig = {};
|
||||
const result = listAgentMailAccountIds(cfg);
|
||||
expect(result).toEqual(["default"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveDefaultAgentMailAccountId", () => {
|
||||
it("returns default account id", () => {
|
||||
const cfg: CoreConfig = {};
|
||||
const result = resolveDefaultAgentMailAccountId(cfg);
|
||||
expect(result).toBe("default");
|
||||
});
|
||||
});
|
||||
|
||||
@ -6,22 +6,6 @@ import type {
|
||||
ResolvedAgentMailAccount,
|
||||
} from "./utils.js";
|
||||
|
||||
/**
|
||||
* Lists all AgentMail account IDs.
|
||||
* Currently supports only a single default account.
|
||||
*/
|
||||
export function listAgentMailAccountIds(_cfg: CoreConfig): string[] {
|
||||
return [DEFAULT_ACCOUNT_ID];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default AgentMail account ID.
|
||||
* Currently only supports a single default account.
|
||||
*/
|
||||
export function resolveDefaultAgentMailAccountId(_cfg: CoreConfig): string {
|
||||
return DEFAULT_ACCOUNT_ID;
|
||||
}
|
||||
|
||||
/** Resolved AgentMail credentials and paths. */
|
||||
export type ResolvedAgentMailCredentials = {
|
||||
apiKey?: string;
|
||||
@ -30,10 +14,10 @@ export type ResolvedAgentMailCredentials = {
|
||||
webhookPath: string;
|
||||
};
|
||||
|
||||
const DEFAULT_WEBHOOK_PATH = "/webhooks/agentmail";
|
||||
export const DEFAULT_WEBHOOK_PATH = "/webhooks/agentmail";
|
||||
|
||||
/** Extracts the path from a URL string. Returns undefined if just root "/". */
|
||||
function extractPathFromUrl(url: string): string | undefined {
|
||||
export function extractPathFromUrl(url: string): string | undefined {
|
||||
try {
|
||||
const parsed = new URL(url);
|
||||
// Return undefined if just root path "/" - use default instead
|
||||
|
||||
@ -8,12 +8,7 @@ import {
|
||||
type ChannelPlugin,
|
||||
} from "clawdbot/plugin-sdk";
|
||||
|
||||
import {
|
||||
listAgentMailAccountIds,
|
||||
resolveAgentMailAccount,
|
||||
resolveDefaultAgentMailAccountId,
|
||||
resolveCredentials,
|
||||
} from "./accounts.js";
|
||||
import { resolveAgentMailAccount, resolveCredentials } from "./accounts.js";
|
||||
import { getAgentMailClient } from "./client.js";
|
||||
import { AgentMailConfigSchema } from "./config-schema.js";
|
||||
import { agentmailOnboardingAdapter } from "./onboarding.js";
|
||||
@ -47,11 +42,10 @@ export const agentmailPlugin: ChannelPlugin<ResolvedAgentMailAccount> = {
|
||||
configSchema: buildChannelConfigSchema(AgentMailConfigSchema),
|
||||
|
||||
config: {
|
||||
listAccountIds: (cfg) => listAgentMailAccountIds(cfg as CoreConfig),
|
||||
listAccountIds: () => [DEFAULT_ACCOUNT_ID],
|
||||
resolveAccount: (cfg, accountId) =>
|
||||
resolveAgentMailAccount({ cfg: cfg as CoreConfig, accountId }),
|
||||
defaultAccountId: (cfg) =>
|
||||
resolveDefaultAgentMailAccountId(cfg as CoreConfig),
|
||||
defaultAccountId: () => DEFAULT_ACCOUNT_ID,
|
||||
setAccountEnabled: ({ cfg, accountId, enabled }) =>
|
||||
setAccountEnabledInConfigSection({
|
||||
cfg: cfg as CoreConfig,
|
||||
@ -102,18 +96,10 @@ export const agentmailPlugin: ChannelPlugin<ResolvedAgentMailAccount> = {
|
||||
"Add email addresses or domains to channels.agentmail.allowFrom",
|
||||
normalizeEntry: (raw) => raw.toLowerCase().trim(),
|
||||
}),
|
||||
collectWarnings: ({ account }) => {
|
||||
const warnings: string[] = [];
|
||||
const { allowFrom = [] } = account.config;
|
||||
|
||||
if (allowFrom.length === 0) {
|
||||
warnings.push(
|
||||
"- AgentMail: No allowFrom configured. All senders will be allowed."
|
||||
);
|
||||
}
|
||||
|
||||
return warnings;
|
||||
},
|
||||
collectWarnings: ({ account }) =>
|
||||
(account.config.allowFrom?.length ?? 0) === 0
|
||||
? ["- AgentMail: No allowFrom configured. All senders will be allowed."]
|
||||
: [],
|
||||
},
|
||||
|
||||
messaging: {
|
||||
@ -147,25 +133,19 @@ export const agentmailPlugin: ChannelPlugin<ResolvedAgentMailAccount> = {
|
||||
},
|
||||
applyAccountConfig: ({ cfg, input }) => {
|
||||
const existing = (cfg as CoreConfig).channels?.agentmail ?? {};
|
||||
const updates: Record<string, unknown> = { enabled: true };
|
||||
if (!input.useEnv) {
|
||||
if (input.token?.trim()) updates.token = input.token.trim();
|
||||
if (input.emailAddress?.trim())
|
||||
updates.emailAddress = input.emailAddress.trim();
|
||||
if (input.webhookPath?.trim())
|
||||
updates.webhookPath = input.webhookPath.trim();
|
||||
}
|
||||
return {
|
||||
...cfg,
|
||||
channels: {
|
||||
...(cfg as CoreConfig).channels,
|
||||
agentmail: {
|
||||
...existing,
|
||||
enabled: true,
|
||||
...(input.useEnv
|
||||
? {}
|
||||
: {
|
||||
...(input.token?.trim() ? { token: input.token.trim() } : {}),
|
||||
...(input.emailAddress?.trim()
|
||||
? { emailAddress: input.emailAddress.trim() }
|
||||
: {}),
|
||||
...(input.webhookPath?.trim()
|
||||
? { webhookPath: input.webhookPath.trim() }
|
||||
: {}),
|
||||
}),
|
||||
},
|
||||
agentmail: { ...existing, ...updates },
|
||||
},
|
||||
};
|
||||
},
|
||||
@ -182,54 +162,39 @@ export const agentmailPlugin: ChannelPlugin<ResolvedAgentMailAccount> = {
|
||||
lastError: null,
|
||||
},
|
||||
collectStatusIssues: (accounts) =>
|
||||
accounts.flatMap((account) => {
|
||||
const lastError =
|
||||
typeof account.lastError === "string" ? account.lastError.trim() : "";
|
||||
if (!lastError) return [];
|
||||
return [
|
||||
{
|
||||
channel: "agentmail",
|
||||
accountId: account.accountId,
|
||||
kind: "runtime",
|
||||
message: `Channel error: ${lastError}`,
|
||||
},
|
||||
];
|
||||
}),
|
||||
buildChannelSummary: ({ snapshot }) => ({
|
||||
configured: snapshot.configured ?? false,
|
||||
emailAddress: snapshot.emailAddress ?? null,
|
||||
running: snapshot.running ?? false,
|
||||
lastStartAt: snapshot.lastStartAt ?? null,
|
||||
lastStopAt: snapshot.lastStopAt ?? null,
|
||||
lastError: snapshot.lastError ?? null,
|
||||
probe: snapshot.probe,
|
||||
lastProbeAt: snapshot.lastProbeAt ?? null,
|
||||
accounts
|
||||
.filter((a) => typeof a.lastError === "string" && a.lastError.trim())
|
||||
.map((a) => ({
|
||||
channel: "agentmail",
|
||||
accountId: a.accountId,
|
||||
kind: "runtime" as const,
|
||||
message: `Channel error: ${a.lastError}`,
|
||||
})),
|
||||
buildChannelSummary: ({ snapshot: s }) => ({
|
||||
configured: s.configured ?? false,
|
||||
emailAddress: s.emailAddress ?? null,
|
||||
running: s.running ?? false,
|
||||
lastStartAt: s.lastStartAt ?? null,
|
||||
lastStopAt: s.lastStopAt ?? null,
|
||||
lastError: s.lastError ?? null,
|
||||
probe: s.probe,
|
||||
lastProbeAt: s.lastProbeAt ?? null,
|
||||
}),
|
||||
probeAccount: async ({ cfg }) => {
|
||||
try {
|
||||
const { apiKey, inboxId } = resolveCredentials(cfg as CoreConfig);
|
||||
|
||||
if (!apiKey || !inboxId) {
|
||||
if (!apiKey || !inboxId)
|
||||
return {
|
||||
ok: false,
|
||||
error: "Missing token or email address",
|
||||
elapsedMs: 0,
|
||||
};
|
||||
}
|
||||
|
||||
const start = Date.now();
|
||||
const client = getAgentMailClient(apiKey);
|
||||
|
||||
// Probe by getting inbox info
|
||||
const inbox = await client.inboxes.get(inboxId);
|
||||
const elapsedMs = Date.now() - start;
|
||||
|
||||
const inbox = await getAgentMailClient(apiKey).inboxes.get(inboxId);
|
||||
return {
|
||||
ok: true,
|
||||
elapsedMs,
|
||||
meta: {
|
||||
inboxId: inbox.inboxId, // inboxId is the email address
|
||||
},
|
||||
elapsedMs: Date.now() - start,
|
||||
meta: { inboxId: inbox.inboxId },
|
||||
};
|
||||
} catch (err) {
|
||||
return {
|
||||
@ -239,40 +204,35 @@ export const agentmailPlugin: ChannelPlugin<ResolvedAgentMailAccount> = {
|
||||
};
|
||||
}
|
||||
},
|
||||
buildAccountSnapshot: ({ account, runtime, probe }) => ({
|
||||
accountId: account.accountId,
|
||||
name: account.name,
|
||||
enabled: account.enabled,
|
||||
configured: account.configured,
|
||||
emailAddress: account.inboxId,
|
||||
running: runtime?.running ?? false,
|
||||
lastStartAt: runtime?.lastStartAt ?? null,
|
||||
lastStopAt: runtime?.lastStopAt ?? null,
|
||||
lastError: runtime?.lastError ?? null,
|
||||
buildAccountSnapshot: ({ account: a, runtime: r, probe }) => ({
|
||||
accountId: a.accountId,
|
||||
name: a.name,
|
||||
enabled: a.enabled,
|
||||
configured: a.configured,
|
||||
emailAddress: a.inboxId,
|
||||
running: r?.running ?? false,
|
||||
lastStartAt: r?.lastStartAt ?? null,
|
||||
lastStopAt: r?.lastStopAt ?? null,
|
||||
lastError: r?.lastError ?? null,
|
||||
probe,
|
||||
lastProbeAt: runtime?.lastProbeAt ?? null,
|
||||
lastInboundAt: runtime?.lastInboundAt ?? null,
|
||||
lastOutboundAt: runtime?.lastOutboundAt ?? null,
|
||||
lastProbeAt: r?.lastProbeAt ?? null,
|
||||
lastInboundAt: r?.lastInboundAt ?? null,
|
||||
lastOutboundAt: r?.lastOutboundAt ?? null,
|
||||
}),
|
||||
},
|
||||
|
||||
gateway: {
|
||||
startAccount: async (ctx) => {
|
||||
const account = ctx.account;
|
||||
ctx.setStatus({
|
||||
accountId: account.accountId,
|
||||
emailAddress: account.inboxId,
|
||||
});
|
||||
const { accountId, inboxId } = ctx.account;
|
||||
ctx.setStatus({ accountId, emailAddress: inboxId });
|
||||
ctx.log?.info(
|
||||
`[${account.accountId}] starting AgentMail provider (email: ${
|
||||
account.inboxId ?? "unknown"
|
||||
`[${accountId}] starting AgentMail provider (email: ${
|
||||
inboxId ?? "unknown"
|
||||
})`
|
||||
);
|
||||
|
||||
// Lazy import: avoid ESM init cycles
|
||||
const { monitorAgentMailProvider } = await import("./monitor.js");
|
||||
return monitorAgentMailProvider({
|
||||
accountId: account.accountId,
|
||||
accountId,
|
||||
abortSignal: ctx.abortSignal,
|
||||
});
|
||||
},
|
||||
|
||||
@ -2,43 +2,24 @@ import type { AgentMailClient } from "agentmail";
|
||||
|
||||
import type { AgentMailConfig } from "./utils.js";
|
||||
|
||||
/**
|
||||
* Checks if a sender email matches any entry in a list.
|
||||
* Entries can be exact email addresses or domains.
|
||||
*/
|
||||
/** Checks if a sender email matches any entry in a list (exact email or domain). */
|
||||
export function matchesList(senderEmail: string, list: string[]): boolean {
|
||||
if (!list || list.length === 0) return false;
|
||||
|
||||
const normalizedSender = senderEmail.toLowerCase().trim();
|
||||
const domain = normalizedSender.split("@")[1];
|
||||
|
||||
return list.some((entry) => {
|
||||
const normalizedEntry = entry.toLowerCase().trim();
|
||||
// Match exact email or domain
|
||||
return normalizedEntry === normalizedSender || normalizedEntry === domain;
|
||||
if (!list?.length) return false;
|
||||
const sender = senderEmail.toLowerCase().trim();
|
||||
const domain = sender.split("@")[1];
|
||||
return list.some((e) => {
|
||||
const entry = e.toLowerCase().trim();
|
||||
return entry === sender || entry === domain;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a sender is allowed based on allowFrom config.
|
||||
*
|
||||
* Logic:
|
||||
* 1. If allowFrom is empty → allowed (open mode)
|
||||
* 2. If sender matches allowFrom → allowed
|
||||
* 3. Otherwise → not allowed
|
||||
*/
|
||||
/** Checks if sender is allowed: empty allowFrom = open mode (all allowed). */
|
||||
export function checkSenderAllowed(
|
||||
senderEmail: string,
|
||||
config: Pick<AgentMailConfig, "allowFrom">,
|
||||
config: Pick<AgentMailConfig, "allowFrom">
|
||||
): boolean {
|
||||
const { allowFrom = [] } = config;
|
||||
|
||||
// Open mode: all senders allowed when allowFrom is empty
|
||||
if (allowFrom.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return matchesList(senderEmail, allowFrom);
|
||||
return allowFrom.length === 0 || matchesList(senderEmail, allowFrom);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -47,7 +28,7 @@ export function checkSenderAllowed(
|
||||
export async function labelMessageAllowed(
|
||||
client: AgentMailClient,
|
||||
inboxId: string,
|
||||
messageId: string,
|
||||
messageId: string
|
||||
): Promise<void> {
|
||||
await client.inboxes.messages.update(inboxId, messageId, {
|
||||
addLabels: ["allowed"],
|
||||
|
||||
@ -2,7 +2,7 @@ import type { IncomingMessage, ServerResponse } from "node:http";
|
||||
|
||||
import { registerPluginHttpRoute } from "clawdbot/plugin-sdk";
|
||||
|
||||
import { resolveAgentMailAccount, resolveCredentials } from "./accounts.js";
|
||||
import { resolveCredentials } from "./accounts.js";
|
||||
import { getAgentMailClient } from "./client.js";
|
||||
import { getAgentMailRuntime } from "./runtime.js";
|
||||
import { checkSenderAllowed, labelMessageAllowed } from "./filtering.js";
|
||||
@ -16,30 +16,26 @@ export type MonitorAgentMailOptions = {
|
||||
};
|
||||
|
||||
// Runtime state tracking
|
||||
const runtimeState = new Map<
|
||||
string,
|
||||
{
|
||||
running: boolean;
|
||||
lastStartAt: number | null;
|
||||
lastStopAt: number | null;
|
||||
lastError: string | null;
|
||||
lastInboundAt?: number | null;
|
||||
lastOutboundAt?: number | null;
|
||||
}
|
||||
>();
|
||||
type RuntimeState = {
|
||||
running: boolean;
|
||||
lastStartAt: number | null;
|
||||
lastStopAt: number | null;
|
||||
lastError: string | null;
|
||||
lastInboundAt?: number | null;
|
||||
lastOutboundAt?: number | null;
|
||||
};
|
||||
const runtimeState = new Map<string, RuntimeState>();
|
||||
const defaultState: RuntimeState = {
|
||||
running: false,
|
||||
lastStartAt: null,
|
||||
lastStopAt: null,
|
||||
lastError: null,
|
||||
};
|
||||
|
||||
function recordState(
|
||||
accountId: string,
|
||||
state: Partial<typeof runtimeState extends Map<string, infer V> ? V : never>
|
||||
) {
|
||||
function recordState(accountId: string, state: Partial<RuntimeState>) {
|
||||
const key = `agentmail:${accountId}`;
|
||||
runtimeState.set(key, {
|
||||
...(runtimeState.get(key) ?? {
|
||||
running: false,
|
||||
lastStartAt: null,
|
||||
lastStopAt: null,
|
||||
lastError: null,
|
||||
}),
|
||||
...(runtimeState.get(key) ?? defaultState),
|
||||
...state,
|
||||
});
|
||||
}
|
||||
@ -87,27 +83,13 @@ export async function monitorAgentMailProvider(
|
||||
module: "agentmail-auto-reply",
|
||||
});
|
||||
const logVerbose = (msg: string) => {
|
||||
if (core.logging.shouldLogVerbose()) {
|
||||
if (logger.debug) {
|
||||
logger.debug(msg);
|
||||
} else {
|
||||
logger.info(msg);
|
||||
}
|
||||
}
|
||||
if (core.logging.shouldLogVerbose()) (logger.debug ?? logger.info)(msg);
|
||||
};
|
||||
|
||||
const accountId = opts.accountId ?? "default";
|
||||
const account = resolveAgentMailAccount({ cfg, accountId });
|
||||
|
||||
if (!account.configured) {
|
||||
logger.warn("AgentMail not configured (missing token or email address)");
|
||||
return;
|
||||
}
|
||||
|
||||
const { apiKey, inboxId, webhookPath } = resolveCredentials(cfg);
|
||||
|
||||
if (!apiKey || !inboxId) {
|
||||
logger.warn("AgentMail token or email address not found");
|
||||
logger.warn("AgentMail not configured (missing token or email address)");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@ -6,9 +6,10 @@ import type {
|
||||
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "clawdbot/plugin-sdk";
|
||||
|
||||
import {
|
||||
DEFAULT_WEBHOOK_PATH,
|
||||
extractPathFromUrl,
|
||||
resolveAgentMailAccount,
|
||||
resolveCredentials,
|
||||
resolveDefaultAgentMailAccountId,
|
||||
} from "./accounts.js";
|
||||
import type { AgentMailConfig, CoreConfig } from "./utils.js";
|
||||
|
||||
@ -86,12 +87,9 @@ export const agentmailOnboardingAdapter: ChannelOnboardingAdapter = {
|
||||
},
|
||||
|
||||
configure: async ({ cfg, prompter, accountOverrides }) => {
|
||||
const defaultAccountId = resolveDefaultAgentMailAccountId(
|
||||
cfg as CoreConfig
|
||||
);
|
||||
const accountId = accountOverrides.agentmail
|
||||
? normalizeAccountId(accountOverrides.agentmail)
|
||||
: defaultAccountId;
|
||||
: DEFAULT_ACCOUNT_ID;
|
||||
|
||||
let next = cfg as MoltbotConfig;
|
||||
const account = resolveAgentMailAccount({
|
||||
@ -160,9 +158,6 @@ export const agentmailOnboardingAdapter: ChannelOnboardingAdapter = {
|
||||
// Apply config
|
||||
next = updateAgentMailConfig(next, { enabled: true, token, emailAddress });
|
||||
|
||||
// Webhook configuration
|
||||
const DEFAULT_WEBHOOK_PATH = "/webhooks/agentmail";
|
||||
|
||||
// Ask if gateway has a public URL
|
||||
const hasPublicUrl = await prompter.confirm({
|
||||
message:
|
||||
@ -174,7 +169,6 @@ export const agentmailOnboardingAdapter: ChannelOnboardingAdapter = {
|
||||
let webhookPath = DEFAULT_WEBHOOK_PATH;
|
||||
|
||||
if (hasPublicUrl) {
|
||||
// Get the full public webhook URL
|
||||
webhookUrl = String(
|
||||
await prompter.text({
|
||||
message: "Full webhook URL",
|
||||
@ -185,21 +179,13 @@ export const agentmailOnboardingAdapter: ChannelOnboardingAdapter = {
|
||||
if (
|
||||
!trimmed.startsWith("http://") &&
|
||||
!trimmed.startsWith("https://")
|
||||
) {
|
||||
)
|
||||
return "Must start with http:// or https://";
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
})
|
||||
).trim();
|
||||
|
||||
// Derive path from URL
|
||||
try {
|
||||
const parsed = new URL(webhookUrl);
|
||||
webhookPath = parsed.pathname || DEFAULT_WEBHOOK_PATH;
|
||||
} catch {
|
||||
webhookPath = DEFAULT_WEBHOOK_PATH;
|
||||
}
|
||||
webhookPath = extractPathFromUrl(webhookUrl) ?? DEFAULT_WEBHOOK_PATH;
|
||||
|
||||
// Auto-register webhook with AgentMail
|
||||
try {
|
||||
@ -349,22 +335,21 @@ async function promptForNewInbox(
|
||||
await prompter.note(`Your new inbox: ${emailAddress}`, "Inbox Created");
|
||||
return emailAddress;
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
const isConflict =
|
||||
message.toLowerCase().includes("already") ||
|
||||
message.toLowerCase().includes("taken") ||
|
||||
message.toLowerCase().includes("exists") ||
|
||||
message.includes("409");
|
||||
|
||||
if (isConflict) {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
const lower = msg.toLowerCase();
|
||||
if (
|
||||
lower.includes("already") ||
|
||||
lower.includes("taken") ||
|
||||
lower.includes("exists") ||
|
||||
msg.includes("409")
|
||||
) {
|
||||
await prompter.note(
|
||||
`${targetEmail} is already taken. Please try a different address.`,
|
||||
"Address Unavailable"
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new Error(`Failed to create inbox: ${message}`);
|
||||
throw new Error(`Failed to create inbox: ${msg}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,62 +16,39 @@ export function extractMessageBody(
|
||||
return msg.extractedText ?? msg.extractedHtml ?? msg.text ?? msg.html ?? "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats thread-level metadata.
|
||||
*/
|
||||
function formatThreadHeader(thread: Thread): string {
|
||||
const lines: string[] = [];
|
||||
|
||||
if (thread.subject) {
|
||||
lines.push(`Subject: ${thread.subject}`);
|
||||
}
|
||||
|
||||
lines.push(`Senders: ${thread.senders.join(", ")}`);
|
||||
lines.push(`Recipients: ${thread.recipients.join(", ")}`);
|
||||
lines.push(`Messages: ${thread.messageCount}`);
|
||||
|
||||
return lines.join("\n");
|
||||
return [
|
||||
thread.subject && `Subject: ${thread.subject}`,
|
||||
`Senders: ${thread.senders.join(", ")}`,
|
||||
`Recipients: ${thread.recipients.join(", ")}`,
|
||||
`Messages: ${thread.messageCount}`,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats recipients for a message (to, cc, bcc).
|
||||
*/
|
||||
function formatMessageRecipients(msg: Message): string {
|
||||
const parts: string[] = [`To: ${msg.to.join(", ")}`];
|
||||
|
||||
if (msg.cc?.length) {
|
||||
parts.push(`Cc: ${msg.cc.join(", ")}`);
|
||||
}
|
||||
|
||||
if (msg.bcc?.length) {
|
||||
parts.push(`Bcc: ${msg.bcc.join(", ")}`);
|
||||
}
|
||||
|
||||
return parts.join("\n");
|
||||
return [
|
||||
`To: ${msg.to.join(", ")}`,
|
||||
msg.cc?.length && `Cc: ${msg.cc.join(", ")}`,
|
||||
msg.bcc?.length && `Bcc: ${msg.bcc.join(", ")}`,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a single message for context display.
|
||||
*/
|
||||
function formatMessage(msg: Message): string {
|
||||
const recipients = formatMessageRecipients(msg);
|
||||
const attachments = formatAttachments(msg.attachments);
|
||||
const body = extractMessageBody(msg);
|
||||
const timestamp = formatUtcDate(msg.createdAt);
|
||||
|
||||
const parts = [
|
||||
`--- ${timestamp} ---`,
|
||||
return [
|
||||
`--- ${formatUtcDate(msg.createdAt)} ---`,
|
||||
`From: ${msg.from}`,
|
||||
recipients,
|
||||
];
|
||||
|
||||
if (attachments) {
|
||||
parts.push(attachments);
|
||||
}
|
||||
|
||||
parts.push("", body);
|
||||
|
||||
return parts.join("\n");
|
||||
formatMessageRecipients(msg),
|
||||
attachments,
|
||||
"",
|
||||
extractMessageBody(msg),
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("\n");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Loading…
Reference in New Issue
Block a user