refactor(config): share whatsapp zod schemas

This commit is contained in:
Peter Steinberger
2026-02-14 21:57:22 +00:00
parent 153601f98b
commit 2954cdabf9

View File

@@ -11,137 +11,108 @@ import {
const ToolPolicyBySenderSchema = z.record(z.string(), ToolPolicySchema).optional();
export const WhatsAppAccountSchema = z
const WhatsAppGroupEntrySchema = z
.object({
name: z.string().optional(),
capabilities: z.array(z.string()).optional(),
markdown: MarkdownConfigSchema,
configWrites: z.boolean().optional(),
enabled: z.boolean().optional(),
sendReadReceipts: z.boolean().optional(),
messagePrefix: z.string().optional(),
responsePrefix: z.string().optional(),
/** Override auth directory for this WhatsApp account (Baileys multi-file auth state). */
authDir: z.string().optional(),
dmPolicy: DmPolicySchema.optional().default("pairing"),
selfChatMode: z.boolean().optional(),
allowFrom: z.array(z.string()).optional(),
groupAllowFrom: z.array(z.string()).optional(),
groupPolicy: GroupPolicySchema.optional().default("allowlist"),
historyLimit: z.number().int().min(0).optional(),
dmHistoryLimit: z.number().int().min(0).optional(),
dms: z.record(z.string(), DmConfigSchema.optional()).optional(),
textChunkLimit: z.number().int().positive().optional(),
chunkMode: z.enum(["length", "newline"]).optional(),
mediaMaxMb: z.number().int().positive().optional(),
blockStreaming: z.boolean().optional(),
blockStreamingCoalesce: BlockStreamingCoalesceSchema.optional(),
groups: z
.record(
z.string(),
z
.object({
requireMention: z.boolean().optional(),
tools: ToolPolicySchema,
toolsBySender: ToolPolicyBySenderSchema,
})
.strict()
.optional(),
)
.optional(),
ackReaction: z
.object({
emoji: z.string().optional(),
direct: z.boolean().optional().default(true),
group: z.enum(["always", "mentions", "never"]).optional().default("mentions"),
})
.strict()
.optional(),
debounceMs: z.number().int().nonnegative().optional().default(0),
heartbeat: ChannelHeartbeatVisibilitySchema,
requireMention: z.boolean().optional(),
tools: ToolPolicySchema,
toolsBySender: ToolPolicyBySenderSchema,
})
.strict()
.optional();
const WhatsAppGroupsSchema = z.record(z.string(), WhatsAppGroupEntrySchema).optional();
const WhatsAppAckReactionSchema = z
.object({
emoji: z.string().optional(),
direct: z.boolean().optional().default(true),
group: z.enum(["always", "mentions", "never"]).optional().default("mentions"),
})
.strict()
.optional();
const WhatsAppSharedSchema = z.object({
capabilities: z.array(z.string()).optional(),
markdown: MarkdownConfigSchema,
configWrites: z.boolean().optional(),
sendReadReceipts: z.boolean().optional(),
messagePrefix: z.string().optional(),
responsePrefix: z.string().optional(),
dmPolicy: DmPolicySchema.optional().default("pairing"),
selfChatMode: z.boolean().optional(),
allowFrom: z.array(z.string()).optional(),
groupAllowFrom: z.array(z.string()).optional(),
groupPolicy: GroupPolicySchema.optional().default("allowlist"),
historyLimit: z.number().int().min(0).optional(),
dmHistoryLimit: z.number().int().min(0).optional(),
dms: z.record(z.string(), DmConfigSchema.optional()).optional(),
textChunkLimit: z.number().int().positive().optional(),
chunkMode: z.enum(["length", "newline"]).optional(),
blockStreaming: z.boolean().optional(),
blockStreamingCoalesce: BlockStreamingCoalesceSchema.optional(),
groups: WhatsAppGroupsSchema,
ackReaction: WhatsAppAckReactionSchema,
debounceMs: z.number().int().nonnegative().optional().default(0),
heartbeat: ChannelHeartbeatVisibilitySchema,
});
function enforceOpenDmPolicyAllowFromStar(params: {
dmPolicy: unknown;
allowFrom: unknown;
ctx: z.RefinementCtx;
message: string;
}) {
if (params.dmPolicy !== "open") {
return;
}
const allow = (Array.isArray(params.allowFrom) ? params.allowFrom : [])
.map((v) => String(v).trim())
.filter(Boolean);
if (allow.includes("*")) {
return;
}
params.ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["allowFrom"],
message: params.message,
});
}
export const WhatsAppAccountSchema = WhatsAppSharedSchema.extend({
name: z.string().optional(),
enabled: z.boolean().optional(),
/** Override auth directory for this WhatsApp account (Baileys multi-file auth state). */
authDir: z.string().optional(),
mediaMaxMb: z.number().int().positive().optional(),
})
.strict()
.superRefine((value, ctx) => {
if (value.dmPolicy !== "open") {
return;
}
const allow = (value.allowFrom ?? []).map((v) => String(v).trim()).filter(Boolean);
if (allow.includes("*")) {
return;
}
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["allowFrom"],
enforceOpenDmPolicyAllowFromStar({
dmPolicy: value.dmPolicy,
allowFrom: value.allowFrom,
ctx,
message: 'channels.whatsapp.accounts.*.dmPolicy="open" requires allowFrom to include "*"',
});
});
export const WhatsAppConfigSchema = z
.object({
accounts: z.record(z.string(), WhatsAppAccountSchema.optional()).optional(),
capabilities: z.array(z.string()).optional(),
markdown: MarkdownConfigSchema,
configWrites: z.boolean().optional(),
sendReadReceipts: z.boolean().optional(),
dmPolicy: DmPolicySchema.optional().default("pairing"),
messagePrefix: z.string().optional(),
responsePrefix: z.string().optional(),
selfChatMode: z.boolean().optional(),
allowFrom: z.array(z.string()).optional(),
groupAllowFrom: z.array(z.string()).optional(),
groupPolicy: GroupPolicySchema.optional().default("allowlist"),
historyLimit: z.number().int().min(0).optional(),
dmHistoryLimit: z.number().int().min(0).optional(),
dms: z.record(z.string(), DmConfigSchema.optional()).optional(),
textChunkLimit: z.number().int().positive().optional(),
chunkMode: z.enum(["length", "newline"]).optional(),
mediaMaxMb: z.number().int().positive().optional().default(50),
blockStreaming: z.boolean().optional(),
blockStreamingCoalesce: BlockStreamingCoalesceSchema.optional(),
actions: z
.object({
reactions: z.boolean().optional(),
sendMessage: z.boolean().optional(),
polls: z.boolean().optional(),
})
.strict()
.optional(),
groups: z
.record(
z.string(),
z
.object({
requireMention: z.boolean().optional(),
tools: ToolPolicySchema,
toolsBySender: ToolPolicyBySenderSchema,
})
.strict()
.optional(),
)
.optional(),
ackReaction: z
.object({
emoji: z.string().optional(),
direct: z.boolean().optional().default(true),
group: z.enum(["always", "mentions", "never"]).optional().default("mentions"),
})
.strict()
.optional(),
debounceMs: z.number().int().nonnegative().optional().default(0),
heartbeat: ChannelHeartbeatVisibilitySchema,
})
export const WhatsAppConfigSchema = WhatsAppSharedSchema.extend({
accounts: z.record(z.string(), WhatsAppAccountSchema.optional()).optional(),
mediaMaxMb: z.number().int().positive().optional().default(50),
actions: z
.object({
reactions: z.boolean().optional(),
sendMessage: z.boolean().optional(),
polls: z.boolean().optional(),
})
.strict()
.optional(),
})
.strict()
.superRefine((value, ctx) => {
if (value.dmPolicy !== "open") {
return;
}
const allow = (value.allowFrom ?? []).map((v) => String(v).trim()).filter(Boolean);
if (allow.includes("*")) {
return;
}
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["allowFrom"],
enforceOpenDmPolicyAllowFromStar({
dmPolicy: value.dmPolicy,
allowFrom: value.allowFrom,
ctx,
message:
'channels.whatsapp.dmPolicy="open" requires channels.whatsapp.allowFrom to include "*"',
});