import { z } from "zod";
import { containsCarriageReturnOrLineFeed } from "./utils/strings.js";
export const HARD_MAX_RECIPIENTS = 50;
export const HARD_MAX_ATTACHMENTS = 10;
export const HARD_MAX_ATTACHMENT_BYTES = 5_000_000;
export const HARD_MAX_TEXT_CHARS = 100_000;
export const HARD_MAX_HTML_CHARS = 200_000;
export const HARD_MAX_SUBJECT_CHARS = 256;
const noHeaderInjection = (value: string): boolean => !containsCarriageReturnOrLineFeed(value);
const emailInputSchema = z
.string()
.min(3)
.max(320)
.refine(noHeaderInjection, "Invalid header characters.");
const emailListSchema = z
.union([emailInputSchema, z.array(emailInputSchema).max(HARD_MAX_RECIPIENTS)])
.transform((value) => (Array.isArray(value) ? value : [value]));
const subjectSchema = z
.string()
.min(1)
.max(HARD_MAX_SUBJECT_CHARS)
.refine(noHeaderInjection, "Invalid header characters.");
const bodySchema = (maxChars: number) => z.string().max(maxChars);
const attachmentSchema = z.object({
filename: z.string().min(1).max(256),
content_base64: z.string().min(4),
content_type: z.string().max(128).optional(),
});
export const listAccountsSchema = z
.object({
account_id: z.string().min(1).max(64).optional(),
})
.strict();
export const verifyAccountSchema = z
.object({
account_id: z.string().min(1).max(64).default("default"),
})
.strict();
export const sendMessageSchema = z
.object({
account_id: z.string().min(1).max(64).default("default"),
from: emailInputSchema.optional(),
to: emailListSchema,
cc: emailListSchema.optional(),
bcc: emailListSchema.optional(),
reply_to: emailInputSchema.optional(),
subject: subjectSchema,
text_body: bodySchema(HARD_MAX_TEXT_CHARS).optional(),
html_body: bodySchema(HARD_MAX_HTML_CHARS).optional(),
attachments: z.array(attachmentSchema).max(HARD_MAX_ATTACHMENTS).optional(),
dry_run: z.boolean().optional(),
})
.strict()
.refine((value) => value.text_body || value.html_body, {
message: "Either text_body or html_body is required.",
path: ["text_body"],
});
export type ListAccountsInput = z.infer<typeof listAccountsSchema>;
export type VerifyAccountInput = z.infer<typeof verifyAccountSchema>;
export type SendMessageInput = z.infer<typeof sendMessageSchema>;
export interface ToolResponse<T> {
readonly summary: string;
readonly data: T;
readonly _meta?: Record<string, unknown>;
}
const accountMetadataSchema = z
.object({
account_id: z.string().min(1).max(64),
host: z.string().min(1).max(256),
port: z.number().int().nonnegative(),
secure: z.boolean(),
default_from: z.string().min(3).max(320).optional(),
})
.strict();
const envelopeSchema = z
.object({
from: z.string().min(3).max(320),
to: z.array(z.string().min(3).max(320)).min(1).max(HARD_MAX_RECIPIENTS),
cc: z.array(z.string().min(3).max(320)).max(HARD_MAX_RECIPIENTS).optional(),
bcc: z.array(z.string().min(3).max(320)).max(HARD_MAX_RECIPIENTS).optional(),
})
.strict();
export const listAccountsResultSchema = z
.object({
accounts: z.array(accountMetadataSchema).max(50),
})
.strict();
export const verifyAccountResultSchema = z
.object({
account_id: z.string().min(1).max(64),
status: z.literal("ok"),
})
.strict();
export const sendMessageResultSchema = z
.object({
account_id: z.string().min(1).max(64),
dry_run: z.boolean(),
envelope: envelopeSchema,
message_id: z.string().min(1).max(256).optional(),
accepted: z.array(z.string().min(3).max(320)).max(HARD_MAX_RECIPIENTS).optional(),
rejected: z.array(z.string().min(3).max(320)).max(HARD_MAX_RECIPIENTS).optional(),
size_bytes_estimate: z.number().int().nonnegative().optional(),
})
.strict();