Extensions: add pi.sendUserMessage() for sending user messages

Adds sendUserMessage() to the extension API, allowing extensions to send
actual user messages (role: user) rather than custom messages. Unlike
sendMessage(), this always triggers a turn and behaves as if the user
typed the message.

- Add SendUserMessageHandler type and sendUserMessage() to ExtensionAPI
- Wire handler through loader, runner, and all modes
- Implement via prompt() with expandPromptTemplates: false
- Add send-user-message.ts example with /ask, /steer, /followup commands
- Document in extensions.md

fixes #483
This commit is contained in:
Mario Zechner 2026-01-06 13:40:24 +01:00
parent f023af0dab
commit 7210086677
13 changed files with 222 additions and 1 deletions

View file

@ -29,6 +29,7 @@ import type {
RegisteredCommand,
RegisteredTool,
SendMessageHandler,
SendUserMessageHandler,
SetActiveToolsHandler,
ToolDefinition,
} from "./types.js";
@ -117,6 +118,7 @@ function createExtensionAPI(
flagValues: Map<string, boolean | string>;
shortcuts: Map<KeyId, ExtensionShortcut>;
setSendMessageHandler: (handler: SendMessageHandler) => void;
setSendUserMessageHandler: (handler: SendUserMessageHandler) => void;
setAppendEntryHandler: (handler: AppendEntryHandler) => void;
setGetActiveToolsHandler: (handler: GetActiveToolsHandler) => void;
setGetAllToolsHandler: (handler: GetAllToolsHandler) => void;
@ -124,6 +126,7 @@ function createExtensionAPI(
setFlagValue: (name: string, value: boolean | string) => void;
} {
let sendMessageHandler: SendMessageHandler = () => {};
let sendUserMessageHandler: SendUserMessageHandler = () => {};
let appendEntryHandler: AppendEntryHandler = () => {};
let getActiveToolsHandler: GetActiveToolsHandler = () => [];
let getAllToolsHandler: GetAllToolsHandler = () => [];
@ -185,6 +188,10 @@ function createExtensionAPI(
sendMessageHandler(message, options);
},
sendUserMessage(content, options): void {
sendUserMessageHandler(content, options);
},
appendEntry(customType: string, data?: unknown): void {
appendEntryHandler(customType, data);
},
@ -218,6 +225,9 @@ function createExtensionAPI(
setSendMessageHandler: (handler: SendMessageHandler) => {
sendMessageHandler = handler;
},
setSendUserMessageHandler: (handler: SendUserMessageHandler) => {
sendUserMessageHandler = handler;
},
setAppendEntryHandler: (handler: AppendEntryHandler) => {
appendEntryHandler = handler;
},
@ -261,6 +271,7 @@ async function loadExtensionWithBun(
flagValues,
shortcuts,
setSendMessageHandler,
setSendUserMessageHandler,
setAppendEntryHandler,
setGetActiveToolsHandler,
setGetAllToolsHandler,
@ -282,6 +293,7 @@ async function loadExtensionWithBun(
flagValues,
shortcuts,
setSendMessageHandler,
setSendUserMessageHandler,
setAppendEntryHandler,
setGetActiveToolsHandler,
setGetAllToolsHandler,
@ -341,6 +353,7 @@ async function loadExtension(
flagValues,
shortcuts,
setSendMessageHandler,
setSendUserMessageHandler,
setAppendEntryHandler,
setGetActiveToolsHandler,
setGetAllToolsHandler,
@ -362,6 +375,7 @@ async function loadExtension(
flagValues,
shortcuts,
setSendMessageHandler,
setSendUserMessageHandler,
setAppendEntryHandler,
setGetActiveToolsHandler,
setGetAllToolsHandler,
@ -396,6 +410,7 @@ export function loadExtensionFromFactory(
flagValues,
shortcuts,
setSendMessageHandler,
setSendUserMessageHandler,
setAppendEntryHandler,
setGetActiveToolsHandler,
setGetAllToolsHandler,
@ -416,6 +431,7 @@ export function loadExtensionFromFactory(
flagValues,
shortcuts,
setSendMessageHandler,
setSendUserMessageHandler,
setAppendEntryHandler,
setGetActiveToolsHandler,
setGetAllToolsHandler,