Commit 4768d4b8d34e

Vincent Demeester <vincent@sbr.pm>
2026-03-17 13:14:48
feat: add ask_user tool for AI-driven questions
Added a simple tool that lets the AI ask the user a question and wait for their response. Supports both free-form text input and optional suggestions the user can pick from or type their own answer.
1 parent 218a2ab
Changed files (1)
dots
pi
agent
extensions
dots/pi/agent/extensions/ask-user.ts
@@ -0,0 +1,93 @@
+/**
+ * Ask User Tool
+ *
+ * Allows the AI to ask the user a question and wait for their response.
+ * Similar to Claude Code's "ask" tool. Supports both free-form text input
+ * and optional multiple-choice suggestions.
+ *
+ * Usage by the AI:
+ *   ask_user({ question: "Which database should I use?" })
+ *   ask_user({ question: "How should I handle errors?", suggestions: ["retry", "fail fast", "log and continue"] })
+ */
+
+import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
+import { Type } from "@sinclair/typebox";
+
+export default function (pi: ExtensionAPI) {
+	pi.registerTool({
+		name: "ask_user",
+		label: "Ask User",
+		description:
+			"Ask the user a question and wait for their response. Use when you need clarification, a decision, or user input to proceed. Supports optional suggestions the user can pick from or ignore.",
+		promptGuidelines: [
+			"Use ask_user when you need user input, clarification, or a decision before proceeding.",
+			"Prefer ask_user over guessing when the choice significantly affects the outcome.",
+			"Keep questions concise and specific. Provide suggestions when there are obvious options.",
+		],
+		parameters: Type.Object({
+			question: Type.String({ description: "The question to ask the user" }),
+			suggestions: Type.Optional(
+				Type.Array(Type.String(), {
+					description:
+						"Optional list of suggested answers. User can pick one or type their own response.",
+				}),
+			),
+		}),
+
+		async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
+			if (!ctx.hasUI) {
+				return {
+					content: [
+						{
+							type: "text",
+							text: "Error: Cannot ask user — running in non-interactive mode",
+						},
+					],
+					details: { question: params.question, answer: null },
+				};
+			}
+
+			const { question, suggestions } = params;
+			const hasSuggestions = suggestions && suggestions.length > 0;
+
+			let answer: string | undefined;
+
+			if (hasSuggestions) {
+				// Show suggestions as selectable options + free-form input
+				const freeFormOption = "✎ Type a different answer…";
+				const options = [...suggestions, freeFormOption];
+				const choice = await ctx.ui.select(question, options);
+
+				if (choice === undefined) {
+					// User cancelled
+					return {
+						content: [{ type: "text", text: "User cancelled — did not answer the question." }],
+						details: { question, suggestions, answer: null },
+					};
+				}
+
+				if (choice === freeFormOption) {
+					// User chose to type their own answer
+					answer = await ctx.ui.input(question);
+				} else {
+					answer = choice;
+				}
+			} else {
+				// Free-form text input
+				answer = await ctx.ui.input(question);
+			}
+
+			if (answer === undefined || answer.trim() === "") {
+				return {
+					content: [{ type: "text", text: "User cancelled — did not answer the question." }],
+					details: { question, suggestions, answer: null },
+				};
+			}
+
+			return {
+				content: [{ type: "text", text: `User answered: ${answer}` }],
+				details: { question, suggestions, answer },
+			};
+		},
+	});
+}