From fd6b7197e1c16c82a6e94b82a00f32ab8522e37f Mon Sep 17 00:00:00 2001 From: Thomas Kosiewski Date: Fri, 23 Oct 2026 17:58:48 +0000 Subject: [PATCH] perf: keep toolDefinitions out of the main chunk (#7363) openAskUserQuestions.ts (eager via StreamingMessageAggregator) imported TOOL_DEFINITIONS only to parse ask_user_question args, which put every tool schema into main-*.js. Move the ask_user_question arg schemas to askUserQuestionSchemas.ts (zod only); toolDefinitions re-exports them. --- .../utils/messages/openAskUserQuestions.ts | 6 +- .../utils/tools/askUserQuestionSchemas.ts | 61 ++++++++++++++++++ .../utils/tools/toolDefinitions.test.ts | 8 +++ src/common/utils/tools/toolDefinitions.ts | 64 +++---------------- 4 files changed, 83 insertions(+), 56 deletions(-) create mode 100644 src/common/utils/tools/askUserQuestionSchemas.ts diff --git a/src/browser/utils/messages/openAskUserQuestions.ts b/src/browser/utils/messages/openAskUserQuestions.ts index c21faba06c2..8b299568046 100644 --- a/src/browser/utils/messages/openAskUserQuestions.ts +++ b/src/browser/utils/messages/openAskUserQuestions.ts @@ -5,7 +5,9 @@ import { getAskUserQuestionOccurrences, isAskUserQuestionOccurrenceOpen, } from "@/common/utils/tools/askUserQuestionSummary"; -import { TOOL_DEFINITIONS } from "@/common/utils/tools/toolDefinitions"; +// Not toolDefinitions.ts: this module is in the main chunk (via the aggregator), and that file +// would bring every tool schema with it (#7363). +import { AskUserQuestionToolArgsSchema } from "@/common/utils/tools/askUserQuestionSchemas"; // Which ask_user_question rows still take answers. One selector serves the dock above the chat // input, the transcript stubs and transcript truncation (StreamingMessageAggregator keeps these @@ -70,7 +72,7 @@ export type AskUserQuestionToolMessage = Extract { + const labels = question.options.map((o) => o.label); + const labelSet = new Set(labels); + if (labelSet.size !== labels.length) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Option labels must be unique within a question", + path: ["options"], + }); + } + + // Claude Code provides "Other" automatically; do not include it explicitly. + if (labels.some((label) => label.trim().toLowerCase() === "other")) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Do not include an 'Other' option; it is provided automatically", + path: ["options"], + }); + } + }); + +export const AskUserQuestionToolArgsSchema = z + .object({ + questions: z.array(AskUserQuestionQuestionSchema).min(1).max(4), + // Optional prefilled answers (Claude Code supports this, though Xum typically won't use it) + answers: z.record(z.string(), z.string()).nullish(), + }) + .strict() + .superRefine((args, ctx) => { + const questionTexts = args.questions.map((q) => q.question); + const questionTextSet = new Set(questionTexts); + if (questionTextSet.size !== questionTexts.length) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Question text must be unique across questions", + path: ["questions"], + }); + } + }); diff --git a/src/common/utils/tools/toolDefinitions.test.ts b/src/common/utils/tools/toolDefinitions.test.ts index 8d894c23f44..015822aaf1a 100644 --- a/src/common/utils/tools/toolDefinitions.test.ts +++ b/src/common/utils/tools/toolDefinitions.test.ts @@ -10,6 +10,7 @@ import { TOOL_DEFINITIONS, WorkflowRunToolArgsSchema, } from "./toolDefinitions"; +import { AskUserQuestionToolArgsSchema as LightAskUserQuestionToolArgsSchema } from "./askUserQuestionSchemas"; describe("TOOL_DEFINITIONS", () => { it("accepts custom subagent_type IDs (deprecated alias)", () => { @@ -578,6 +579,13 @@ describe("TOOL_DEFINITIONS", () => { }); }); + // The renderer's open-question selector (openAskUserQuestions.ts) and the aggregator's + // truncation rule parse args with the light module's schema (kept out of the main chunk, + // #7363); it must stay the schema the tool itself validates with. + it("ask_user_question validates with the light askUserQuestionSchemas module's schema", () => { + expect(TOOL_DEFINITIONS.ask_user_question.schema).toBe(LightAskUserQuestionToolArgsSchema); + }); + it("accepts ask_user_question headers longer than 12 characters", () => { const parsed = TOOL_DEFINITIONS.ask_user_question.schema.safeParse({ questions: [ diff --git a/src/common/utils/tools/toolDefinitions.ts b/src/common/utils/tools/toolDefinitions.ts index 0d5214e6282..9d94bf5858a 100644 --- a/src/common/utils/tools/toolDefinitions.ts +++ b/src/common/utils/tools/toolDefinitions.ts @@ -100,46 +100,21 @@ import { INSTANCE_DISCOVERY_DEFAULT_LIMIT, INSTANCE_DISCOVERY_MAX_LIMIT, } from "@/constants/agentMessaging"; +import { + AskUserQuestionOptionSchema, + AskUserQuestionQuestionSchema, + AskUserQuestionToolArgsSchema, +} from "./askUserQuestionSchemas"; // ----------------------------------------------------------------------------- // ask_user_question (interactive questions for top-level agents) // ----------------------------------------------------------------------------- -export const AskUserQuestionOptionSchema = z - .object({ - label: z.string().min(1), - description: z.string().min(1), - }) - .strict(); - -export const AskUserQuestionQuestionSchema = z - .object({ - question: z.string().min(1), - header: z.string().min(1).max(32).describe("Short UI label"), - options: z.array(AskUserQuestionOptionSchema).min(2).max(4), - multiSelect: z.boolean(), - }) - .strict() - .superRefine((question, ctx) => { - const labels = question.options.map((o) => o.label); - const labelSet = new Set(labels); - if (labelSet.size !== labels.length) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: "Option labels must be unique within a question", - path: ["options"], - }); - } - - // Claude Code provides "Other" automatically; do not include it explicitly. - if (labels.some((label) => label.trim().toLowerCase() === "other")) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: "Do not include an 'Other' option; it is provided automatically", - path: ["options"], - }); - } - }); +export { + AskUserQuestionOptionSchema, + AskUserQuestionQuestionSchema, + AskUserQuestionToolArgsSchema, +}; const AskUserQuestionUiOnlySchema = z.object({ questions: z.array(AskUserQuestionQuestionSchema), @@ -171,25 +146,6 @@ const ToolOutputUiOnlyFieldSchema = { ui_only: ToolOutputUiOnlySchema.optional(), }; -export const AskUserQuestionToolArgsSchema = z - .object({ - questions: z.array(AskUserQuestionQuestionSchema).min(1).max(4), - // Optional prefilled answers (Claude Code supports this, though Xum typically won't use it) - answers: z.record(z.string(), z.string()).nullish(), - }) - .strict() - .superRefine((args, ctx) => { - const questionTexts = args.questions.map((q) => q.question); - const questionTextSet = new Set(questionTexts); - if (questionTextSet.size !== questionTexts.length) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: "Question text must be unique across questions", - path: ["questions"], - }); - } - }); - const AskUserQuestionToolSummarySchema = z .object({ summary: z.string(),