forked from Mirror/pi
feat(agent): expose expandPromptTemplates in sendUserMessage (#7857)
This commit is contained in:
@@ -1426,12 +1426,16 @@ pi.sendUserMessage([
|
||||
// During streaming - must specify delivery mode
|
||||
pi.sendUserMessage("Focus on error handling", { deliverAs: "steer" });
|
||||
pi.sendUserMessage("And then summarize", { deliverAs: "followUp" });
|
||||
|
||||
// Opt in to extension command dispatch and skill/prompt template expansion
|
||||
pi.sendUserMessage("/review src/index.ts", { expandPromptTemplates: true });
|
||||
```
|
||||
|
||||
**Options:**
|
||||
- `deliverAs` - Required when agent is streaming:
|
||||
- `"steer"` - Queues the message for delivery after the current assistant turn finishes executing its tool calls
|
||||
- `"followUp"` - Waits for agent to finish all tools
|
||||
- `expandPromptTemplates` - Dispatch extension commands and expand skill commands and prompt templates. Defaults to `false`.
|
||||
|
||||
When not streaming, the message is sent immediately and triggers a new turn. When streaming without `deliverAs`, throws an error.
|
||||
|
||||
|
||||
@@ -238,7 +238,7 @@ export interface ExtensionBindings {
|
||||
|
||||
/** Options for AgentSession.prompt() */
|
||||
export interface PromptOptions {
|
||||
/** Whether to expand file-based prompt templates (default: true) */
|
||||
/** Whether to dispatch extension commands and expand skill commands and prompt templates (default: true) */
|
||||
expandPromptTemplates?: boolean;
|
||||
/** Image attachments */
|
||||
images?: ImageContent[];
|
||||
@@ -1476,10 +1476,11 @@ export class AgentSession {
|
||||
*
|
||||
* @param content User message content (string or content array)
|
||||
* @param options.deliverAs Delivery mode when streaming: "steer" or "followUp"
|
||||
* @param options.expandPromptTemplates Whether to dispatch extension commands and expand skill commands and prompt templates. Default: false.
|
||||
*/
|
||||
async sendUserMessage(
|
||||
content: string | (TextContent | ImageContent)[],
|
||||
options?: { deliverAs?: "steer" | "followUp" },
|
||||
options?: { deliverAs?: "steer" | "followUp"; expandPromptTemplates?: boolean },
|
||||
): Promise<void> {
|
||||
// Normalize content to text string + optional images
|
||||
let text: string;
|
||||
@@ -1501,9 +1502,8 @@ export class AgentSession {
|
||||
if (images.length === 0) images = undefined;
|
||||
}
|
||||
|
||||
// Use prompt() with expandPromptTemplates: false to skip command handling and template expansion
|
||||
await this.prompt(text, {
|
||||
expandPromptTemplates: false,
|
||||
expandPromptTemplates: options?.expandPromptTemplates ?? false,
|
||||
streamingBehavior: options?.deliverAs,
|
||||
images,
|
||||
source: "extension",
|
||||
|
||||
@@ -399,7 +399,7 @@ export interface ReplacedSessionContext extends ExtensionCommandContext {
|
||||
|
||||
sendUserMessage(
|
||||
content: string | (TextContent | ImageContent)[],
|
||||
options?: { deliverAs?: "steer" | "followUp" },
|
||||
options?: { deliverAs?: "steer" | "followUp"; expandPromptTemplates?: boolean },
|
||||
): Promise<void>;
|
||||
}
|
||||
|
||||
@@ -1307,10 +1307,11 @@ export interface ExtensionAPI {
|
||||
/**
|
||||
* Send a user message to the agent. Always triggers a turn.
|
||||
* When the agent is streaming, use deliverAs to specify how to queue the message.
|
||||
* Set expandPromptTemplates to dispatch extension commands and expand skill commands and prompt templates.
|
||||
*/
|
||||
sendUserMessage(
|
||||
content: string | (TextContent | ImageContent)[],
|
||||
options?: { deliverAs?: "steer" | "followUp" },
|
||||
options?: { deliverAs?: "steer" | "followUp"; expandPromptTemplates?: boolean },
|
||||
): void;
|
||||
|
||||
/** Append a custom entry to the session for state persistence (not sent to LLM). */
|
||||
@@ -1560,7 +1561,7 @@ export type SendMessageHandler = <T = unknown>(
|
||||
|
||||
export type SendUserMessageHandler = (
|
||||
content: string | (TextContent | ImageContent)[],
|
||||
options?: { deliverAs?: "steer" | "followUp" },
|
||||
options?: { deliverAs?: "steer" | "followUp"; expandPromptTemplates?: boolean },
|
||||
) => void;
|
||||
|
||||
export type AppendEntryHandler = <T = unknown>(customType: string, data?: T) => void;
|
||||
|
||||
@@ -5,7 +5,7 @@ import type { AgentTool } from "@earendil-works/pi-agent-core";
|
||||
import { fauxAssistantMessage, fauxToolCall, type Model } from "@earendil-works/pi-ai";
|
||||
import { Type } from "typebox";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import type { InputEvent } from "../../src/core/extensions/index.ts";
|
||||
import type { ExtensionAPI, InputEvent } from "../../src/core/extensions/index.ts";
|
||||
import type { PromptTemplate } from "../../src/core/prompt-templates.ts";
|
||||
import { createSyntheticSourceInfo } from "../../src/core/source-info.ts";
|
||||
import { createTestResourceLoader } from "../utilities.ts";
|
||||
@@ -224,6 +224,39 @@ describe("AgentSession prompt characterization", () => {
|
||||
expect(expandedPrompt).toBe("Review this code: src/index.ts");
|
||||
});
|
||||
|
||||
it("sendUserMessage can opt into prompt template expansion", async () => {
|
||||
const template: PromptTemplate = {
|
||||
name: "review",
|
||||
description: "Review template",
|
||||
content: "Review this code: $1",
|
||||
filePath: "/virtual/review.md",
|
||||
sourceInfo: createSyntheticSourceInfo("/virtual/review.md", {
|
||||
source: "local",
|
||||
scope: "temporary",
|
||||
origin: "top-level",
|
||||
}),
|
||||
};
|
||||
const resourceLoader = {
|
||||
...createTestResourceLoader(),
|
||||
getPrompts: () => ({ prompts: [template], diagnostics: [] }),
|
||||
};
|
||||
const harness = await createHarness({ resourceLoader });
|
||||
harnesses.push(harness);
|
||||
let expandedPrompt = "";
|
||||
|
||||
harness.setResponses([
|
||||
(context) => {
|
||||
const user = context.messages.find((message) => message.role === "user");
|
||||
expandedPrompt = user ? getMessageText(user) : "";
|
||||
return fauxAssistantMessage("ok");
|
||||
},
|
||||
]);
|
||||
|
||||
await harness.session.sendUserMessage("/review src/index.ts", { expandPromptTemplates: true });
|
||||
|
||||
expect(expandedPrompt).toBe("Review this code: src/index.ts");
|
||||
});
|
||||
|
||||
it("dispatches extension commands without consuming a provider response", async () => {
|
||||
const commandRuns: string[] = [];
|
||||
const harness = await createHarness({
|
||||
@@ -248,6 +281,35 @@ describe("AgentSession prompt characterization", () => {
|
||||
expect(harness.getPendingResponseCount()).toBe(1);
|
||||
});
|
||||
|
||||
it("extension sendUserMessage can opt into extension command dispatch", async () => {
|
||||
let extensionApi: ExtensionAPI | undefined;
|
||||
let resolveCommandRun: (args: string) => void = () => {};
|
||||
const commandRun = new Promise<string>((resolve) => {
|
||||
resolveCommandRun = resolve;
|
||||
});
|
||||
const harness = await createHarness({
|
||||
extensionFactories: [
|
||||
(pi) => {
|
||||
extensionApi = pi;
|
||||
pi.registerCommand("testcmd", {
|
||||
description: "Test command",
|
||||
handler: async (args) => {
|
||||
resolveCommandRun(args);
|
||||
},
|
||||
});
|
||||
},
|
||||
],
|
||||
});
|
||||
harnesses.push(harness);
|
||||
expect(extensionApi).toBeDefined();
|
||||
|
||||
extensionApi?.sendUserMessage("/testcmd hello world", { expandPromptTemplates: true });
|
||||
|
||||
await expect(commandRun).resolves.toBe("hello world");
|
||||
expect(harness.session.messages).toEqual([]);
|
||||
expect(harness.getPendingResponseCount()).toBe(0);
|
||||
});
|
||||
|
||||
it("sendUserMessage while idle triggers a turn", async () => {
|
||||
const harness = await createHarness();
|
||||
harnesses.push(harness);
|
||||
|
||||
Reference in New Issue
Block a user