import { z } from "zod";
import { containsCarriageReturnOrLineFeed } from "./utils/strings.js";
/**
* Hard maximum limits for input validation.
*
* These are absolute upper bounds that cannot be exceeded, regardless of
* policy configuration. They prevent abuse and ensure system stability.
*/
export const HARD_MAX_RECIPIENTS = 50;
export const HARD_MAX_ATTACHMENTS = 10;
export const HARD_MAX_ATTACHMENT_BYTES = 5_000_000; // ~4.8 MB per file
export const HARD_MAX_TEXT_CHARS = 100_000;
export const HARD_MAX_HTML_CHARS = 200_000;
export const HARD_MAX_SUBJECT_CHARS = 256;
/**
* Validate that a string does not contain header injection characters.
*
* Email headers can be injected using CR (\r) and LF (\n) characters.
* This validator ensures such characters are not present in email addresses
* and other header fields.
*
* @param value - The string to validate.
* @returns true if the string is safe, false otherwise.
*/
const noHeaderInjection = (value: string): boolean => !containsCarriageReturnOrLineFeed(value);
/**
* Zod schema for a single email address input.
*
* Validates: length (3-320 chars), no header injection characters.
*/
const emailInputSchema = z
.string()
.min(3)
.max(320)
.refine(noHeaderInjection, "Invalid header characters.");
/**
* Zod schema for email address inputs.
*
* Accepts either a single email address (string) or an array of addresses.
* Automatically transforms single values into an array.
*/
const emailListSchema = z
.union([emailInputSchema, z.array(emailInputSchema).max(HARD_MAX_RECIPIENTS)])
.transform((value) => (Array.isArray(value) ? value : [value]));
/**
* Zod schema for email subject line.
*
* Validates: length (1-HARD_MAX_SUBJECT_CHARS), no header injection characters.
*/
const subjectSchema = z
.string()
.min(1)
.max(HARD_MAX_SUBJECT_CHARS)
.refine(noHeaderInjection, "Invalid header characters.");
/**
* Create a schema for email body content with character limit.
*
* @param maxChars - Maximum allowed characters for the body content.
* @returns A Zod string schema with max length validation.
*/
const bodySchema = (maxChars: number) => z.string().max(maxChars);
/**
* Zod schema for email attachment.
*
* Attachments are provided as base64-encoded content.
*/
const attachmentSchema = z.object({
filename: z.string().min(1).max(256).describe("Attachment filename."),
content_base64: z.string().min(4).describe("Base64-encoded attachment content."),
content_type: z.string().max(128).optional().describe("Optional MIME type."),
});
/**
* Zod schema for list_accounts tool input.
*
* Lists configured SMTP accounts with optional filtering by account ID.
*/
export const listAccountsSchema = z
.object({
account_id: z
.string()
.min(1)
.max(64)
.optional()
.describe("Optional SMTP account identifier to filter the list."),
})
.strict();
/**
* Zod schema for verify_account tool input.
*
* Verifies SMTP account connectivity without sending an email.
*/
export const verifyAccountSchema = z
.object({
account_id: z
.string()
.min(1)
.max(64)
.default("default")
.describe("SMTP account identifier to verify (defaults to 'default')."),
})
.strict();
/**
* Zod schema for send_message tool input.
*
* Sends an outbound email via SMTP with optional attachments.
* Requires at least one of text_body or html_body.
*/
export const sendMessageSchema = z
.object({
account_id: z
.string()
.min(1)
.max(64)
.default("default")
.describe("SMTP account identifier to send with (defaults to 'default')."),
from: emailInputSchema.optional().describe("Override From address for the message."),
to: emailListSchema.describe("Primary recipients (string or list)."),
cc: emailListSchema.optional().describe("Carbon copy recipients (string or list)."),
bcc: emailListSchema.optional().describe("Blind carbon copy recipients (string or list)."),
reply_to: emailInputSchema.optional().describe("Reply-To address for responses."),
subject: subjectSchema.describe("Email subject line."),
text_body: bodySchema(HARD_MAX_TEXT_CHARS).optional().describe("Plain-text message body."),
html_body: bodySchema(HARD_MAX_HTML_CHARS).optional().describe("HTML message body."),
attachments: z
.array(attachmentSchema)
.max(HARD_MAX_ATTACHMENTS)
.optional()
.describe("Optional attachments with base64-encoded content."),
dry_run: z.boolean().optional().describe("Validate and build the message without sending."),
})
.strict()
.refine((value) => value.text_body || value.html_body, {
message: "Either text_body or html_body is required.",
path: ["text_body"],
});
/**
* Input type for list_accounts tool (inferred from schema).
*/
export type ListAccountsInput = z.infer<typeof listAccountsSchema>;
/**
* Input type for verify_account tool (inferred from schema).
*/
export type VerifyAccountInput = z.infer<typeof verifyAccountSchema>;
/**
* Input type for send_message tool (inferred from schema).
*/
export type SendMessageInput = z.infer<typeof sendMessageSchema>;
/**
* Standard response format for tool outputs.
*
* Provides a human-readable summary along with structured data.
* The _meta field can include additional non-critical metadata.
*
* @template T - The type of the data payload.
*/
export interface ToolResponse<T> {
readonly summary: string;
readonly data: T;
readonly _meta?: Record<string, unknown>;
}
/**
* Zod schema for account metadata (non-sensitive account information).
*
* Used when listing accounts - excludes credentials.
*/
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();
/**
* Zod schema for email envelope (sender and recipients).
*
* Represents the envelope used for SMTP transmission.
*/
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();
/**
* Zod schema for list_accounts tool result.
*
* Returns an array of account metadata objects.
*/
export const listAccountsResultSchema = z
.object({
accounts: z.array(accountMetadataSchema).max(50),
})
.strict();
/**
* Zod schema for verify_account tool result.
*
* Confirms successful SMTP connectivity verification.
*/
export const verifyAccountResultSchema = z
.object({
account_id: z.string().min(1).max(64),
status: z.literal("ok"),
})
.strict();
/**
* Zod schema for send_message tool result.
*
* Returns information about the sent message including envelope,
* message ID, and recipient acceptance/rejection status.
*/
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();