@henryqw/pi-ask-question 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/extensions/ask-question.ts +13 -8
- package/package.json +2 -2
|
@@ -17,6 +17,7 @@ interface QuestionOption {
|
|
|
17
17
|
|
|
18
18
|
type DisplayOption = QuestionOption & { isOther?: boolean };
|
|
19
19
|
|
|
20
|
+
const CUSTOM_OPTION_LABEL = "Something else.";
|
|
20
21
|
const NUMBER_KEYS = ["1", "2", "3", "4"] as const;
|
|
21
22
|
|
|
22
23
|
interface QuestionDetails {
|
|
@@ -25,6 +26,7 @@ interface QuestionDetails {
|
|
|
25
26
|
answer: string | null;
|
|
26
27
|
wasCustom?: boolean;
|
|
27
28
|
selectedIndex?: number;
|
|
29
|
+
error?: string;
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
const QuestionOptionSchema = Type.Object({
|
|
@@ -45,10 +47,10 @@ export default function askQuestionExtension(pi: ExtensionAPI): void {
|
|
|
45
47
|
pi.registerTool({
|
|
46
48
|
name: "ask_question",
|
|
47
49
|
label: "Ask Question",
|
|
48
|
-
description: "
|
|
49
|
-
promptSnippet: "
|
|
50
|
+
description: "In interactive TUI sessions, ask user one question with up to three options or a custom answer. First option is shown as recommended.",
|
|
51
|
+
promptSnippet: "In interactive TUI sessions, ask user one question with up to three options or a custom answer",
|
|
50
52
|
promptGuidelines: [
|
|
51
|
-
"
|
|
53
|
+
"In interactive TUI sessions, use ask_question instead of plain assistant text whenever user input is needed to proceed; in non-interactive sessions, ask in plain assistant text.",
|
|
52
54
|
"Give ask_question one to three concise, meaningful options without inventing filler, put recommended option first, and omit '(Recommended)' from its label.",
|
|
53
55
|
"Give ask_question option descriptions only when they explain meaningful tradeoffs; never repeat option labels.",
|
|
54
56
|
],
|
|
@@ -63,9 +65,10 @@ export default function askQuestionExtension(pi: ExtensionAPI): void {
|
|
|
63
65
|
}));
|
|
64
66
|
const options = suppliedOptions.map((option) => option.label);
|
|
65
67
|
if (ctx.mode !== "tui") {
|
|
68
|
+
const error = "UI not available (running in non-interactive mode)";
|
|
66
69
|
return {
|
|
67
|
-
content: [{ type: "text" as const, text:
|
|
68
|
-
details: { question, options, answer: null } satisfies QuestionDetails,
|
|
70
|
+
content: [{ type: "text" as const, text: `Error: ${error}` }],
|
|
71
|
+
details: { question, options, answer: null, error } satisfies QuestionDetails,
|
|
69
72
|
};
|
|
70
73
|
}
|
|
71
74
|
let validationError: string | undefined;
|
|
@@ -73,14 +76,15 @@ export default function askQuestionExtension(pi: ExtensionAPI): void {
|
|
|
73
76
|
else if (!question) validationError = "Question must not be blank";
|
|
74
77
|
else if (suppliedOptions.some((option) => !option.label)) validationError = "Option labels must not be blank";
|
|
75
78
|
else if (new Set(options.map((option) => option.toLowerCase())).size !== options.length) validationError = "Option labels must be unique";
|
|
79
|
+
else if (options.some((option) => option.toLowerCase() === CUSTOM_OPTION_LABEL.toLowerCase())) validationError = `Option label "${CUSTOM_OPTION_LABEL}" is reserved`;
|
|
76
80
|
if (validationError) {
|
|
77
81
|
return {
|
|
78
82
|
content: [{ type: "text" as const, text: `Error: ${validationError}` }],
|
|
79
|
-
details: { question, options, answer: null } satisfies QuestionDetails,
|
|
83
|
+
details: { question, options, answer: null, error: validationError } satisfies QuestionDetails,
|
|
80
84
|
};
|
|
81
85
|
}
|
|
82
86
|
|
|
83
|
-
const allOptions: DisplayOption[] = [...suppliedOptions, { label:
|
|
87
|
+
const allOptions: DisplayOption[] = [...suppliedOptions, { label: CUSTOM_OPTION_LABEL, isOther: true }];
|
|
84
88
|
const result = await ctx.ui.custom<{ answer: string; wasCustom: boolean; index?: number } | null>(
|
|
85
89
|
(tui, theme, _kb, done) => {
|
|
86
90
|
let optionIndex = 0;
|
|
@@ -226,7 +230,7 @@ export default function askQuestionExtension(pi: ExtensionAPI): void {
|
|
|
226
230
|
const options = Array.isArray(args.options) ? args.options : [];
|
|
227
231
|
if (options.length) {
|
|
228
232
|
const labels = options.map((option: QuestionOption) => option.label);
|
|
229
|
-
const numbered = [...labels,
|
|
233
|
+
const numbered = [...labels, CUSTOM_OPTION_LABEL].map((option, index) => `${index + 1}. ${option}${index === 0 ? " (Recommended)" : ""}`);
|
|
230
234
|
text += `\n${theme.fg("dim", ` Options: ${numbered.join(", ")}`)}`;
|
|
231
235
|
}
|
|
232
236
|
return new Text(text, 0, 0);
|
|
@@ -238,6 +242,7 @@ export default function askQuestionExtension(pi: ExtensionAPI): void {
|
|
|
238
242
|
const content = result.content[0];
|
|
239
243
|
return new Text(content?.type === "text" ? content.text : "", 0, 0);
|
|
240
244
|
}
|
|
245
|
+
if (details.error) return new Text(theme.fg("error", `Error: ${details.error}`), 0, 0);
|
|
241
246
|
if (details.answer === null) return new Text(theme.fg("warning", "Cancelled"), 0, 0);
|
|
242
247
|
if (details.wasCustom) {
|
|
243
248
|
return new Text(theme.fg("success", "✓ ") + theme.fg("muted", "(wrote) ") + theme.fg("accent", details.answer), 0, 0);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@henryqw/pi-ask-question",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Ask Pi users one interactive question with choices or a custom answer.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"peerDependencies": {
|
|
28
28
|
"@earendil-works/pi-coding-agent": ">=0.84.1",
|
|
29
29
|
"@earendil-works/pi-tui": ">=0.84.1",
|
|
30
|
-
"typebox": "
|
|
30
|
+
"typebox": "^1.3.13"
|
|
31
31
|
},
|
|
32
32
|
"repository": {
|
|
33
33
|
"type": "git",
|