From e3252b0564e2d23e0d9890d6439c6f25105f61a0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 26 Nov 2025 11:18:42 +0000 Subject: [PATCH 1/2] Initial plan From 3f3341cb6cc82e236528e1877dc7de4e225bbdcf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 26 Nov 2025 11:30:17 +0000 Subject: [PATCH 2/2] Refactor validation to use full predicates and pass false to nested toJSON Co-authored-by: Jiralite <33201955+Jiralite@users.noreply.github.com> --- .../commands/chatInput/Assertions.ts | 73 ++++++++++++++++--- .../commands/chatInput/ChatInputCommand.ts | 2 +- .../chatInput/ChatInputCommandSubcommands.ts | 4 +- 3 files changed, 65 insertions(+), 14 deletions(-) diff --git a/packages/builders/src/interactions/commands/chatInput/Assertions.ts b/packages/builders/src/interactions/commands/chatInput/Assertions.ts index 8d16afd8ac44..cc354fb38a6d 100644 --- a/packages/builders/src/interactions/commands/chatInput/Assertions.ts +++ b/packages/builders/src/interactions/commands/chatInput/Assertions.ts @@ -117,6 +117,48 @@ export const stringOptionPredicate = basicOptionPredicate }) .and(autocompleteOrStringChoicesMixinOptionPredicate); +// Full predicate for all basic option types (used for nested validation in subcommands) +// This validates all possible properties that any basic option might have +const fullBasicOptionPredicate = sharedNameAndDescriptionPredicate + .extend({ + type: basicOptionTypesPredicate, + required: z.boolean().optional(), + // String option properties + max_length: z.number().min(0).max(6_000).optional(), + min_length: z.number().min(1).max(6_000).optional(), + // Number/Integer option properties + max_value: z.union([z.int(), z.float32()]).optional(), + min_value: z.union([z.int(), z.float32()]).optional(), + // Channel option properties + channel_types: z.literal(ApplicationCommandOptionAllowedChannelTypes).array().optional(), + // Choice/Autocomplete properties (shared by String, Number, Integer) + autocomplete: z.boolean().optional(), + choices: z + .union([choiceStringPredicate.array().max(25), choiceNumberPredicate.array().max(25)]) + .optional(), + }) + .refine( + (data) => { + // Validate autocomplete and choices are mutually exclusive + if (data.autocomplete === true && data.choices && data.choices.length > 0) { + return false; + } + return true; + }, + { message: 'Autocomplete and choices are mutually exclusive' }, + ) + .refine( + (data) => { + // Validate integer min/max are integers + if (data.type === ApplicationCommandOptionType.Integer) { + if (data.max_value !== undefined && !Number.isInteger(data.max_value)) return false; + if (data.min_value !== undefined && !Number.isInteger(data.min_value)) return false; + } + return true; + }, + { message: 'Integer options must have integer min/max values' }, + ); + const baseChatInputCommandPredicate = sharedNameAndDescriptionPredicate.extend({ contexts: z.array(z.enum(InteractionContextType)).optional(), default_member_permissions: memberPermissionsPredicate.optional(), @@ -124,26 +166,35 @@ const baseChatInputCommandPredicate = sharedNameAndDescriptionPredicate.extend({ nsfw: z.boolean().optional(), }); -// Because you can only add options via builders, there's no need to validate whole objects here otherwise -const chatInputCommandOptionsPredicate = z.union([ - z.object({ type: basicOptionTypesPredicate }).array(), - z.object({ type: z.literal(ApplicationCommandOptionType.Subcommand) }).array(), - z.object({ type: z.literal(ApplicationCommandOptionType.SubcommandGroup) }).array(), +// Full predicate for subcommand (used for nested validation in subcommand groups) +const fullSubcommandPredicate = sharedNameAndDescriptionPredicate.extend({ + type: z.literal(ApplicationCommandOptionType.Subcommand), + options: z.array(fullBasicOptionPredicate).max(25).optional(), +}); + +// Full predicate for subcommand group (used for nested validation in chat input commands) +const fullSubcommandGroupPredicate = sharedNameAndDescriptionPredicate.extend({ + type: z.literal(ApplicationCommandOptionType.SubcommandGroup), + options: z.array(fullSubcommandPredicate).min(1).max(25), +}); + +// Full options predicate for chat input commands (validates complete nested structure) +const fullChatInputCommandOptionsPredicate = z.union([ + z.array(fullBasicOptionPredicate), + z.array(fullSubcommandPredicate), + z.array(fullSubcommandGroupPredicate), ]); export const chatInputCommandPredicate = baseChatInputCommandPredicate.extend({ - options: chatInputCommandOptionsPredicate.optional(), + options: fullChatInputCommandOptionsPredicate.optional(), }); export const chatInputCommandSubcommandGroupPredicate = sharedNameAndDescriptionPredicate.extend({ type: z.literal(ApplicationCommandOptionType.SubcommandGroup), - options: z - .array(z.object({ type: z.literal(ApplicationCommandOptionType.Subcommand) })) - .min(1) - .max(25), + options: z.array(fullSubcommandPredicate).min(1).max(25), }); export const chatInputCommandSubcommandPredicate = sharedNameAndDescriptionPredicate.extend({ type: z.literal(ApplicationCommandOptionType.Subcommand), - options: z.array(z.object({ type: basicOptionTypesPredicate })).max(25), + options: z.array(fullBasicOptionPredicate).max(25).optional(), }); diff --git a/packages/builders/src/interactions/commands/chatInput/ChatInputCommand.ts b/packages/builders/src/interactions/commands/chatInput/ChatInputCommand.ts index 4d90c2cceff9..e5f59125c553 100644 --- a/packages/builders/src/interactions/commands/chatInput/ChatInputCommand.ts +++ b/packages/builders/src/interactions/commands/chatInput/ChatInputCommand.ts @@ -30,7 +30,7 @@ export class ChatInputCommandBuilder extends Mixin( const data: RESTPostAPIChatInputApplicationCommandsJSONBody = { ...structuredClone(rest as Omit), type: ApplicationCommandType.ChatInput, - options: options?.map((option) => option.toJSON(validationOverride)), + options: options?.map((option) => option.toJSON(false)), }; validate(chatInputCommandPredicate, data, validationOverride); diff --git a/packages/builders/src/interactions/commands/chatInput/ChatInputCommandSubcommands.ts b/packages/builders/src/interactions/commands/chatInput/ChatInputCommandSubcommands.ts index ae6256be7f27..b3626fab38bf 100644 --- a/packages/builders/src/interactions/commands/chatInput/ChatInputCommandSubcommands.ts +++ b/packages/builders/src/interactions/commands/chatInput/ChatInputCommandSubcommands.ts @@ -74,7 +74,7 @@ export class ChatInputCommandSubcommandGroupBuilder const data = { ...(structuredClone(rest) as Omit), type: ApplicationCommandOptionType.SubcommandGroup as const, - options: options?.map((option) => option.toJSON(validationOverride)) ?? [], + options: options?.map((option) => option.toJSON(false)) ?? [], }; validate(chatInputCommandSubcommandGroupPredicate, data, validationOverride); @@ -107,7 +107,7 @@ export class ChatInputCommandSubcommandBuilder const data = { ...(structuredClone(rest) as Omit), type: ApplicationCommandOptionType.Subcommand as const, - options: options?.map((option) => option.toJSON(validationOverride)) ?? [], + options: options?.map((option) => option.toJSON(false)) ?? [], }; validate(chatInputCommandSubcommandPredicate, data, validationOverride);