import { z } from "zod"; import { BlockStreamingChunkSchema, BlockStreamingCoalesceSchema, DmConfigSchema, DmPolicySchema, ExecutableTokenSchema, GroupPolicySchema, MSTeamsReplyStyleSchema, ProviderCommandsSchema, ReplyToModeSchema, RetryConfigSchema, requireOpenAllowFrom, } from "./zod-schema.core.js"; import { normalizeTelegramCommandDescription, normalizeTelegramCommandName, resolveTelegramCustomCommands, } from "./telegram-custom-commands.js"; const TelegramInlineButtonsScopeSchema = z.enum(["off", "dm", "group", "all", "allowlist"]); const TelegramCapabilitiesSchema = z.union([ z.array(z.string()), z.object({ inlineButtons: TelegramInlineButtonsScopeSchema.optional(), }), ]); export const TelegramTopicSchema = z.object({ requireMention: z.boolean().optional(), skills: z.array(z.string()).optional(), enabled: z.boolean().optional(), allowFrom: z.array(z.union([z.string(), z.number()])).optional(), systemPrompt: z.string().optional(), }); export const TelegramGroupSchema = z.object({ requireMention: z.boolean().optional(), skills: z.array(z.string()).optional(), enabled: z.boolean().optional(), allowFrom: z.array(z.union([z.string(), z.number()])).optional(), systemPrompt: z.string().optional(), topics: z.record(z.string(), TelegramTopicSchema.optional()).optional(), }); const TelegramCustomCommandSchema = z.object({ command: z.string().transform(normalizeTelegramCommandName), description: z.string().transform(normalizeTelegramCommandDescription), }); const validateTelegramCustomCommands = ( value: { customCommands?: Array<{ command?: string; description?: string }> }, ctx: z.RefinementCtx, ) => { if (!value.customCommands || value.customCommands.length === 0) return; const { issues } = resolveTelegramCustomCommands({ commands: value.customCommands, checkReserved: false, checkDuplicates: false, }); for (const issue of issues) { ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["customCommands", issue.index, issue.field], message: issue.message, }); } }; export const TelegramAccountSchemaBase = z.object({ name: z.string().optional(), capabilities: TelegramCapabilitiesSchema.optional(), enabled: z.boolean().optional(), commands: ProviderCommandsSchema, customCommands: z.array(TelegramCustomCommandSchema).optional(), configWrites: z.boolean().optional(), dmPolicy: DmPolicySchema.optional().default("pairing"), botToken: z.string().optional(), tokenFile: z.string().optional(), replyToMode: ReplyToModeSchema.optional(), groups: z.record(z.string(), TelegramGroupSchema.optional()).optional(), allowFrom: z.array(z.union([z.string(), z.number()])).optional(), groupAllowFrom: z.array(z.union([z.string(), z.number()])).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(), blockStreaming: z.boolean().optional(), draftChunk: BlockStreamingChunkSchema.optional(), blockStreamingCoalesce: BlockStreamingCoalesceSchema.optional(), streamMode: z.enum(["off", "partial", "block"]).optional().default("partial"), mediaMaxMb: z.number().positive().optional(), timeoutSeconds: z.number().int().positive().optional(), retry: RetryConfigSchema, proxy: z.string().optional(), webhookUrl: z.string().optional(), webhookSecret: z.string().optional(), webhookPath: z.string().optional(), actions: z .object({ reactions: z.boolean().optional(), sendMessage: z.boolean().optional(), deleteMessage: z.boolean().optional(), }) .optional(), reactionNotifications: z.enum(["off", "own", "all"]).optional(), reactionLevel: z.enum(["off", "ack", "minimal", "extensive"]).optional(), }); export const TelegramAccountSchema = TelegramAccountSchemaBase.superRefine((value, ctx) => { requireOpenAllowFrom({ policy: value.dmPolicy, allowFrom: value.allowFrom, ctx, path: ["allowFrom"], message: 'channels.telegram.dmPolicy="open" requires channels.telegram.allowFrom to include "*"', }); validateTelegramCustomCommands(value, ctx); }); export const TelegramConfigSchema = TelegramAccountSchemaBase.extend({ accounts: z.record(z.string(), TelegramAccountSchema.optional()).optional(), }).superRefine((value, ctx) => { requireOpenAllowFrom({ policy: value.dmPolicy, allowFrom: value.allowFrom, ctx, path: ["allowFrom"], message: 'channels.telegram.dmPolicy="open" requires channels.telegram.allowFrom to include "*"', }); validateTelegramCustomCommands(value, ctx); }); export const DiscordDmSchema = z .object({ enabled: z.boolean().optional(), policy: DmPolicySchema.optional().default("pairing"), allowFrom: z.array(z.union([z.string(), z.number()])).optional(), groupEnabled: z.boolean().optional(), groupChannels: z.array(z.union([z.string(), z.number()])).optional(), }) .superRefine((value, ctx) => { requireOpenAllowFrom({ policy: value.policy, allowFrom: value.allowFrom, ctx, path: ["allowFrom"], message: 'channels.discord.dm.policy="open" requires channels.discord.dm.allowFrom to include "*"', }); }); export const DiscordGuildChannelSchema = z.object({ allow: z.boolean().optional(), requireMention: z.boolean().optional(), skills: z.array(z.string()).optional(), enabled: z.boolean().optional(), users: z.array(z.union([z.string(), z.number()])).optional(), systemPrompt: z.string().optional(), autoThread: z.boolean().optional(), }); export const DiscordGuildSchema = z.object({ slug: z.string().optional(), requireMention: z.boolean().optional(), reactionNotifications: z.enum(["off", "own", "all", "allowlist"]).optional(), users: z.array(z.union([z.string(), z.number()])).optional(), channels: z.record(z.string(), DiscordGuildChannelSchema.optional()).optional(), }); export const DiscordAccountSchema = z.object({ name: z.string().optional(), capabilities: z.array(z.string()).optional(), enabled: z.boolean().optional(), commands: ProviderCommandsSchema, configWrites: z.boolean().optional(), token: z.string().optional(), allowBots: z.boolean().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(), blockStreaming: z.boolean().optional(), blockStreamingCoalesce: BlockStreamingCoalesceSchema.optional(), maxLinesPerMessage: z.number().int().positive().optional(), mediaMaxMb: z.number().positive().optional(), retry: RetryConfigSchema, actions: z .object({ reactions: z.boolean().optional(), stickers: z.boolean().optional(), emojiUploads: z.boolean().optional(), stickerUploads: z.boolean().optional(), polls: z.boolean().optional(), permissions: z.boolean().optional(), messages: z.boolean().optional(), threads: z.boolean().optional(), pins: z.boolean().optional(), search: z.boolean().optional(), memberInfo: z.boolean().optional(), roleInfo: z.boolean().optional(), roles: z.boolean().optional(), channelInfo: z.boolean().optional(), voiceStatus: z.boolean().optional(), events: z.boolean().optional(), moderation: z.boolean().optional(), channels: z.boolean().optional(), }) .optional(), replyToMode: ReplyToModeSchema.optional(), dm: DiscordDmSchema.optional(), guilds: z.record(z.string(), DiscordGuildSchema.optional()).optional(), }); export const DiscordConfigSchema = DiscordAccountSchema.extend({ accounts: z.record(z.string(), DiscordAccountSchema.optional()).optional(), }); export const SlackDmSchema = z .object({ enabled: z.boolean().optional(), policy: DmPolicySchema.optional().default("pairing"), allowFrom: z.array(z.union([z.string(), z.number()])).optional(), groupEnabled: z.boolean().optional(), groupChannels: z.array(z.union([z.string(), z.number()])).optional(), }) .superRefine((value, ctx) => { requireOpenAllowFrom({ policy: value.policy, allowFrom: value.allowFrom, ctx, path: ["allowFrom"], message: 'channels.slack.dm.policy="open" requires channels.slack.dm.allowFrom to include "*"', }); }); export const SlackChannelSchema = z.object({ enabled: z.boolean().optional(), allow: z.boolean().optional(), requireMention: z.boolean().optional(), allowBots: z.boolean().optional(), users: z.array(z.union([z.string(), z.number()])).optional(), skills: z.array(z.string()).optional(), systemPrompt: z.string().optional(), }); export const SlackThreadSchema = z.object({ historyScope: z.enum(["thread", "channel"]).optional(), inheritParent: z.boolean().optional(), }); export const SlackAccountSchema = z.object({ name: z.string().optional(), capabilities: z.array(z.string()).optional(), enabled: z.boolean().optional(), commands: ProviderCommandsSchema, configWrites: z.boolean().optional(), botToken: z.string().optional(), appToken: z.string().optional(), userToken: z.string().optional(), userTokenReadOnly: z.boolean().optional().default(true), allowBots: z.boolean().optional(), requireMention: z.boolean().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(), blockStreaming: z.boolean().optional(), blockStreamingCoalesce: BlockStreamingCoalesceSchema.optional(), mediaMaxMb: z.number().positive().optional(), reactionNotifications: z.enum(["off", "own", "all", "allowlist"]).optional(), reactionAllowlist: z.array(z.union([z.string(), z.number()])).optional(), replyToMode: ReplyToModeSchema.optional(), thread: SlackThreadSchema.optional(), actions: z .object({ reactions: z.boolean().optional(), messages: z.boolean().optional(), pins: z.boolean().optional(), search: z.boolean().optional(), permissions: z.boolean().optional(), memberInfo: z.boolean().optional(), channelInfo: z.boolean().optional(), emojiList: z.boolean().optional(), }) .optional(), slashCommand: z .object({ enabled: z.boolean().optional(), name: z.string().optional(), sessionPrefix: z.string().optional(), ephemeral: z.boolean().optional(), }) .optional(), dm: SlackDmSchema.optional(), channels: z.record(z.string(), SlackChannelSchema.optional()).optional(), }); export const SlackConfigSchema = SlackAccountSchema.extend({ accounts: z.record(z.string(), SlackAccountSchema.optional()).optional(), }); export const SignalAccountSchemaBase = z.object({ name: z.string().optional(), capabilities: z.array(z.string()).optional(), enabled: z.boolean().optional(), configWrites: z.boolean().optional(), account: z.string().optional(), httpUrl: z.string().optional(), httpHost: z.string().optional(), httpPort: z.number().int().positive().optional(), cliPath: ExecutableTokenSchema.optional(), autoStart: z.boolean().optional(), receiveMode: z.union([z.literal("on-start"), z.literal("manual")]).optional(), ignoreAttachments: z.boolean().optional(), ignoreStories: z.boolean().optional(), sendReadReceipts: z.boolean().optional(), dmPolicy: DmPolicySchema.optional().default("pairing"), allowFrom: z.array(z.union([z.string(), z.number()])).optional(), groupAllowFrom: z.array(z.union([z.string(), z.number()])).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(), blockStreaming: z.boolean().optional(), blockStreamingCoalesce: BlockStreamingCoalesceSchema.optional(), mediaMaxMb: z.number().int().positive().optional(), reactionNotifications: z.enum(["off", "own", "all", "allowlist"]).optional(), reactionAllowlist: z.array(z.union([z.string(), z.number()])).optional(), }); export const SignalAccountSchema = SignalAccountSchemaBase.superRefine((value, ctx) => { requireOpenAllowFrom({ policy: value.dmPolicy, allowFrom: value.allowFrom, ctx, path: ["allowFrom"], message: 'channels.signal.dmPolicy="open" requires channels.signal.allowFrom to include "*"', }); }); export const SignalConfigSchema = SignalAccountSchemaBase.extend({ accounts: z.record(z.string(), SignalAccountSchema.optional()).optional(), }).superRefine((value, ctx) => { requireOpenAllowFrom({ policy: value.dmPolicy, allowFrom: value.allowFrom, ctx, path: ["allowFrom"], message: 'channels.signal.dmPolicy="open" requires channels.signal.allowFrom to include "*"', }); }); export const IMessageAccountSchemaBase = z.object({ name: z.string().optional(), capabilities: z.array(z.string()).optional(), enabled: z.boolean().optional(), configWrites: z.boolean().optional(), cliPath: ExecutableTokenSchema.optional(), dbPath: z.string().optional(), remoteHost: z.string().optional(), service: z.union([z.literal("imessage"), z.literal("sms"), z.literal("auto")]).optional(), region: z.string().optional(), dmPolicy: DmPolicySchema.optional().default("pairing"), allowFrom: z.array(z.union([z.string(), z.number()])).optional(), groupAllowFrom: z.array(z.union([z.string(), z.number()])).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(), includeAttachments: z.boolean().optional(), mediaMaxMb: z.number().int().positive().optional(), textChunkLimit: z.number().int().positive().optional(), blockStreaming: z.boolean().optional(), blockStreamingCoalesce: BlockStreamingCoalesceSchema.optional(), groups: z .record( z.string(), z .object({ requireMention: z.boolean().optional(), }) .optional(), ) .optional(), }); export const IMessageAccountSchema = IMessageAccountSchemaBase.superRefine((value, ctx) => { requireOpenAllowFrom({ policy: value.dmPolicy, allowFrom: value.allowFrom, ctx, path: ["allowFrom"], message: 'channels.imessage.dmPolicy="open" requires channels.imessage.allowFrom to include "*"', }); }); export const IMessageConfigSchema = IMessageAccountSchemaBase.extend({ accounts: z.record(z.string(), IMessageAccountSchema.optional()).optional(), }).superRefine((value, ctx) => { requireOpenAllowFrom({ policy: value.dmPolicy, allowFrom: value.allowFrom, ctx, path: ["allowFrom"], message: 'channels.imessage.dmPolicy="open" requires channels.imessage.allowFrom to include "*"', }); }); export const MSTeamsChannelSchema = z.object({ requireMention: z.boolean().optional(), replyStyle: MSTeamsReplyStyleSchema.optional(), }); export const MSTeamsTeamSchema = z.object({ requireMention: z.boolean().optional(), replyStyle: MSTeamsReplyStyleSchema.optional(), channels: z.record(z.string(), MSTeamsChannelSchema.optional()).optional(), }); export const MSTeamsConfigSchema = z .object({ enabled: z.boolean().optional(), capabilities: z.array(z.string()).optional(), configWrites: z.boolean().optional(), appId: z.string().optional(), appPassword: z.string().optional(), tenantId: z.string().optional(), webhook: z .object({ port: z.number().int().positive().optional(), path: z.string().optional(), }) .optional(), dmPolicy: DmPolicySchema.optional().default("pairing"), allowFrom: z.array(z.string()).optional(), groupAllowFrom: z.array(z.string()).optional(), groupPolicy: GroupPolicySchema.optional().default("allowlist"), textChunkLimit: z.number().int().positive().optional(), blockStreamingCoalesce: BlockStreamingCoalesceSchema.optional(), mediaAllowHosts: z.array(z.string()).optional(), requireMention: z.boolean().optional(), historyLimit: z.number().int().min(0).optional(), dmHistoryLimit: z.number().int().min(0).optional(), dms: z.record(z.string(), DmConfigSchema.optional()).optional(), replyStyle: MSTeamsReplyStyleSchema.optional(), teams: z.record(z.string(), MSTeamsTeamSchema.optional()).optional(), }) .superRefine((value, ctx) => { requireOpenAllowFrom({ policy: value.dmPolicy, allowFrom: value.allowFrom, ctx, path: ["allowFrom"], message: 'channels.msteams.dmPolicy="open" requires channels.msteams.allowFrom to include "*"', }); });