diff --git a/packages/opencode/src/question/index.ts b/packages/opencode/src/question/index.ts index d18098a9c4f..41029ecbbdb 100644 --- a/packages/opencode/src/question/index.ts +++ b/packages/opencode/src/question/index.ts @@ -10,7 +10,7 @@ export namespace Question { export const Option = z .object({ - label: z.string().describe("Display text (1-5 words, concise)"), + label: z.string().max(30).describe("Display text (1-5 words, concise)"), description: z.string().describe("Explanation of choice"), }) .meta({ @@ -21,7 +21,7 @@ export namespace Question { export const Info = z .object({ question: z.string().describe("Complete question"), - header: z.string().max(12).describe("Very short label (max 12 chars)"), + header: z.string().max(30).describe("Very short label (max 30 chars)"), options: z.array(Option).describe("Available choices"), multiple: z.boolean().optional().describe("Allow selecting multiple choices"), custom: z.boolean().optional().describe("Allow typing a custom answer (default: true)"), diff --git a/packages/opencode/test/tool/question.test.ts b/packages/opencode/test/tool/question.test.ts new file mode 100644 index 00000000000..9e3f4e25cb2 --- /dev/null +++ b/packages/opencode/test/tool/question.test.ts @@ -0,0 +1,107 @@ +import { describe, expect, test, spyOn, beforeEach, afterEach } from "bun:test" +import { z } from "zod" +import { QuestionTool } from "../../src/tool/question" +import * as QuestionModule from "../../src/question" + +const ctx = { + sessionID: "test-session", + messageID: "test-message", + callID: "test-call", + agent: "test-agent", + abort: AbortSignal.any([]), + metadata: () => {}, + ask: async () => {}, +} + +describe("tool.question", () => { + let askSpy: any; + + beforeEach(() => { + askSpy = spyOn(QuestionModule.Question, "ask").mockImplementation(async () => { + return [] + }) + }) + + afterEach(() => { + askSpy.mockRestore() + }) + + test("should successfully execute with valid question parameters", async () => { + const tool = await QuestionTool.init() + const questions = [ + { + question: "What is your favorite color?", + header: "Color", + options: [ + { label: "Red", description: "The color of passion" }, + { label: "Blue", description: "The color of sky" }, + ], + multiple: false, + }, + ] + + askSpy.mockResolvedValueOnce([["Red"]]) + + const result = await tool.execute( + { questions }, + ctx, + ) + expect(askSpy).toHaveBeenCalledTimes(1) + expect(result.title).toBe("Asked 1 question") + }) + + test("should now pass with a header longer than 12 but less than 30 chars", async () => { + const tool = await QuestionTool.init() + const questions = [ + { + question: "What is your favorite animal?", + header: "This Header is Over 12", + options: [{ label: "Dog", description: "Man's best friend" }], + }, + ] + + askSpy.mockResolvedValueOnce([["Dog"]]) + + const result = await tool.execute({ questions }, ctx) + expect(result.output).toContain(`"What is your favorite animal?"="Dog"`) + }) + + test("should throw an Error for header exceeding 30 characters", async () => { + const tool = await QuestionTool.init() + const questions = [ + { + question: "What is your favorite animal?", + header: "This Header is Definitely More Than Thirty Characters Long", + options: [{ label: "Dog", description: "Man's best friend" }], + }, + ] + try { + await tool.execute({ questions }, ctx) + // If it reaches here, the test should fail + expect(true).toBe(false) + } catch (e: any) { + expect(e).toBeInstanceOf(Error) + expect(e.cause).toBeInstanceOf(z.ZodError) + } + }) + + test("should throw an Error for label exceeding 30 characters", async () => { + const tool = await QuestionTool.init() + const questions = [ + { + question: "A question with a very long label", + header: "Long Label", + options: [{ label: "This is a very, very, very long label that will exceed the limit", description: "A description" }], + }, + ] + try { + await tool.execute({ questions }, ctx) + // If it reaches here, the test should fail + expect(true).toBe(false) + } catch (e: any) { + expect(e).toBeInstanceOf(Error) + expect(e.cause).toBeInstanceOf(z.ZodError) + } + }) +}) +