import { z } from "zod"; export { z }; const DmPolicySchema = z.enum(["open", "pairing", "allowlist"]); const GroupPolicySchema = z.enum(["open", "allowlist", "disabled"]); const ToolPolicySchema = z .object({ allow: z.array(z.string()).optional(), deny: z.array(z.string()).optional(), }) .strict() .optional(); const DmConfigSchema = z .object({ enabled: z.boolean().optional(), systemPrompt: z.string().optional(), }) .strict() .optional(); // Message render mode: raw (default) = plain text, rich_text = POPO rich text format const RenderModeSchema = z.enum(["raw", "rich_text"]).optional(); export const PopoGroupSchema = z .object({ requireMention: z.boolean().optional(), tools: ToolPolicySchema, skills: z.array(z.string()).optional(), enabled: z.boolean().optional(), allowFrom: z.array(z.union([z.string(), z.number()])).optional(), systemPrompt: z.string().optional(), }) .strict(); export type PopoGroupConfig = z.infer; export const PopoConfigSchema = z .object({ enabled: z.boolean().optional(), appKey: z.string().optional(), appSecret: z.string().optional(), token: z.string().optional(), // Token for signature verification aesKey: z.string().optional(), // 32-char AES key for encryption server: z .string() .optional() .default("https://open.popo.netease.com/open-apis/robots/v1"), webhookPath: z.string().optional().default("/popo/events"), webhookPort: z.number().int().positive().optional(), dmPolicy: DmPolicySchema.optional().default("pairing"), allowFrom: z.array(z.union([z.string(), z.number()])).optional(), groupPolicy: GroupPolicySchema.optional().default("allowlist"), groupAllowFrom: z.array(z.union([z.string(), z.number()])).optional(), requireMention: z.boolean().optional().default(true), groups: z.record(z.string(), PopoGroupSchema.optional()).optional(), historyLimit: z.number().int().min(0).optional(), dmHistoryLimit: z.number().int().min(0).optional(), dms: z.record(z.string(), DmConfigSchema).optional(), textChunkLimit: z.number().int().positive().optional(), chunkMode: z.enum(["length", "newline"]).optional(), mediaMaxMb: z.number().positive().optional().default(20), // 20MB max renderMode: RenderModeSchema, // raw = plain text (default), rich_text = POPO rich text }) .strict() .superRefine((value, ctx) => { if (value.dmPolicy === "open") { const allowFrom = value.allowFrom ?? []; const hasWildcard = allowFrom.some((entry) => String(entry).trim() === "*"); if (!hasWildcard) { ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["allowFrom"], message: 'channels.popo.dmPolicy="open" requires channels.popo.allowFrom to include "*"', }); } } });