fix: auto-truncate question option labels exceeding 30 characters
When AI generates AskUserQuestion tool calls with option labels longer than 30 characters, opencode validation rejects them with "too_big" error. This fix adds a pre-tool-use hook that automatically truncates labels to 30 characters (with "..." suffix) before the validation occurs. Fixes the error: "The question tool was called with invalid arguments: expected string to have <=30 characters" Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -30,3 +30,4 @@ export { createTaskResumeInfoHook } from "./task-resume-info";
|
||||
export { createStartWorkHook } from "./start-work";
|
||||
export { createAtlasHook } from "./atlas";
|
||||
export { createDelegateTaskRetryHook } from "./delegate-task-retry";
|
||||
export { createQuestionLabelTruncatorHook } from "./question-label-truncator";
|
||||
|
||||
63
src/hooks/question-label-truncator/index.ts
Normal file
63
src/hooks/question-label-truncator/index.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import type { Plugin } from "@opencode-ai/plugin";
|
||||
|
||||
const MAX_LABEL_LENGTH = 30;
|
||||
|
||||
interface QuestionOption {
|
||||
label: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
interface Question {
|
||||
question: string;
|
||||
header?: string;
|
||||
options: QuestionOption[];
|
||||
multiSelect?: boolean;
|
||||
}
|
||||
|
||||
interface AskUserQuestionArgs {
|
||||
questions: Question[];
|
||||
}
|
||||
|
||||
function truncateLabel(label: string, maxLength: number = MAX_LABEL_LENGTH): string {
|
||||
if (label.length <= maxLength) {
|
||||
return label;
|
||||
}
|
||||
return label.substring(0, maxLength - 3) + "...";
|
||||
}
|
||||
|
||||
function truncateQuestionLabels(args: AskUserQuestionArgs): AskUserQuestionArgs {
|
||||
if (!args.questions || !Array.isArray(args.questions)) {
|
||||
return args;
|
||||
}
|
||||
|
||||
return {
|
||||
...args,
|
||||
questions: args.questions.map((question) => ({
|
||||
...question,
|
||||
options: question.options?.map((option) => ({
|
||||
...option,
|
||||
label: truncateLabel(option.label),
|
||||
})) ?? [],
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
export function createQuestionLabelTruncatorHook(): Pick<
|
||||
Plugin,
|
||||
"tool.execute.before"
|
||||
> {
|
||||
return {
|
||||
"tool.execute.before": async (input, output) => {
|
||||
const toolName = input.tool?.toLowerCase();
|
||||
|
||||
if (toolName === "askuserquestion" || toolName === "ask_user_question") {
|
||||
const args = output.args as AskUserQuestionArgs | undefined;
|
||||
|
||||
if (args?.questions) {
|
||||
const truncatedArgs = truncateQuestionLabels(args);
|
||||
Object.assign(output.args as object, truncatedArgs);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
createStartWorkHook,
|
||||
createAtlasHook,
|
||||
createPrometheusMdOnlyHook,
|
||||
createQuestionLabelTruncatorHook,
|
||||
} from "./hooks";
|
||||
import {
|
||||
contextCollector,
|
||||
@@ -203,6 +204,8 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
|
||||
? createPrometheusMdOnlyHook(ctx)
|
||||
: null;
|
||||
|
||||
const questionLabelTruncator = createQuestionLabelTruncatorHook();
|
||||
|
||||
const taskResumeInfo = createTaskResumeInfoHook();
|
||||
|
||||
const backgroundManager = new BackgroundManager(ctx, pluginConfig.background_task);
|
||||
@@ -484,6 +487,7 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
|
||||
},
|
||||
|
||||
"tool.execute.before": async (input, output) => {
|
||||
await questionLabelTruncator["tool.execute.before"]?.(input, output);
|
||||
await claudeCodeHooks["tool.execute.before"](input, output);
|
||||
await nonInteractiveEnv?.["tool.execute.before"](input, output);
|
||||
await commentChecker?.["tool.execute.before"](input, output);
|
||||
|
||||
Reference in New Issue
Block a user