co-mono/packages/coding-agent/examples/extensions/send-user-message.ts
Mario Zechner 7210086677 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
2026-01-06 13:40:24 +01:00

97 lines
2.8 KiB
TypeScript

/**
* Send User Message Example
*
* Demonstrates pi.sendUserMessage() for sending user messages from extensions.
* Unlike pi.sendMessage() which sends custom messages, sendUserMessage() sends
* actual user messages that appear in the conversation as if typed by the user.
*
* Usage:
* /ask What is 2+2? - Sends a user message (always triggers a turn)
* /steer Focus on X - Sends while streaming with steer delivery
* /followup And then? - Sends while streaming with followUp delivery
*/
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
export default function (pi: ExtensionAPI) {
// Simple command that sends a user message
pi.registerCommand("ask", {
description: "Send a user message to the agent",
handler: async (args, ctx) => {
if (!args.trim()) {
ctx.ui.notify("Usage: /ask <message>", "warning");
return;
}
// sendUserMessage always triggers a turn when not streaming
// If streaming, it will throw (no deliverAs specified)
if (!ctx.isIdle()) {
ctx.ui.notify("Agent is busy. Use /steer or /followup instead.", "warning");
return;
}
pi.sendUserMessage(args);
},
});
// Command that steers the agent mid-conversation
pi.registerCommand("steer", {
description: "Send a steering message (interrupts current processing)",
handler: async (args, ctx) => {
if (!args.trim()) {
ctx.ui.notify("Usage: /steer <message>", "warning");
return;
}
if (ctx.isIdle()) {
// Not streaming, just send normally
pi.sendUserMessage(args);
} else {
// Streaming - use steer to interrupt
pi.sendUserMessage(args, { deliverAs: "steer" });
}
},
});
// Command that queues a follow-up message
pi.registerCommand("followup", {
description: "Queue a follow-up message (waits for current processing)",
handler: async (args, ctx) => {
if (!args.trim()) {
ctx.ui.notify("Usage: /followup <message>", "warning");
return;
}
if (ctx.isIdle()) {
// Not streaming, just send normally
pi.sendUserMessage(args);
} else {
// Streaming - queue as follow-up
pi.sendUserMessage(args, { deliverAs: "followUp" });
ctx.ui.notify("Follow-up queued", "info");
}
},
});
// Example with content array (text + images would go here)
pi.registerCommand("askwith", {
description: "Send a user message with structured content",
handler: async (args, ctx) => {
if (!args.trim()) {
ctx.ui.notify("Usage: /askwith <message>", "warning");
return;
}
if (!ctx.isIdle()) {
ctx.ui.notify("Agent is busy", "warning");
return;
}
// sendUserMessage accepts string or (TextContent | ImageContent)[]
pi.sendUserMessage([
{ type: "text", text: `User request: ${args}` },
{ type: "text", text: "Please respond concisely." },
]);
},
});
}