mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
feat(agent): expose expandPromptTemplates in sendUserMessage (#7857)
This commit is contained in:
parent
4a879dd759
commit
b987ead358
4 changed files with 75 additions and 8 deletions
|
|
@ -1426,12 +1426,16 @@ pi.sendUserMessage([
|
||||||
// During streaming - must specify delivery mode
|
// During streaming - must specify delivery mode
|
||||||
pi.sendUserMessage("Focus on error handling", { deliverAs: "steer" });
|
pi.sendUserMessage("Focus on error handling", { deliverAs: "steer" });
|
||||||
pi.sendUserMessage("And then summarize", { deliverAs: "followUp" });
|
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:**
|
**Options:**
|
||||||
- `deliverAs` - Required when agent is streaming:
|
- `deliverAs` - Required when agent is streaming:
|
||||||
- `"steer"` - Queues the message for delivery after the current assistant turn finishes executing its tool calls
|
- `"steer"` - Queues the message for delivery after the current assistant turn finishes executing its tool calls
|
||||||
- `"followUp"` - Waits for agent to finish all tools
|
- `"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.
|
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() */
|
/** Options for AgentSession.prompt() */
|
||||||
export interface PromptOptions {
|
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;
|
expandPromptTemplates?: boolean;
|
||||||
/** Image attachments */
|
/** Image attachments */
|
||||||
images?: ImageContent[];
|
images?: ImageContent[];
|
||||||
|
|
@ -1476,10 +1476,11 @@ export class AgentSession {
|
||||||
*
|
*
|
||||||
* @param content User message content (string or content array)
|
* @param content User message content (string or content array)
|
||||||
* @param options.deliverAs Delivery mode when streaming: "steer" or "followUp"
|
* @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(
|
async sendUserMessage(
|
||||||
content: string | (TextContent | ImageContent)[],
|
content: string | (TextContent | ImageContent)[],
|
||||||
options?: { deliverAs?: "steer" | "followUp" },
|
options?: { deliverAs?: "steer" | "followUp"; expandPromptTemplates?: boolean },
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
// Normalize content to text string + optional images
|
// Normalize content to text string + optional images
|
||||||
let text: string;
|
let text: string;
|
||||||
|
|
@ -1501,9 +1502,8 @@ export class AgentSession {
|
||||||
if (images.length === 0) images = undefined;
|
if (images.length === 0) images = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use prompt() with expandPromptTemplates: false to skip command handling and template expansion
|
|
||||||
await this.prompt(text, {
|
await this.prompt(text, {
|
||||||
expandPromptTemplates: false,
|
expandPromptTemplates: options?.expandPromptTemplates ?? false,
|
||||||
streamingBehavior: options?.deliverAs,
|
streamingBehavior: options?.deliverAs,
|
||||||
images,
|
images,
|
||||||
source: "extension",
|
source: "extension",
|
||||||
|
|
|
||||||
|
|
@ -399,7 +399,7 @@ export interface ReplacedSessionContext extends ExtensionCommandContext {
|
||||||
|
|
||||||
sendUserMessage(
|
sendUserMessage(
|
||||||
content: string | (TextContent | ImageContent)[],
|
content: string | (TextContent | ImageContent)[],
|
||||||
options?: { deliverAs?: "steer" | "followUp" },
|
options?: { deliverAs?: "steer" | "followUp"; expandPromptTemplates?: boolean },
|
||||||
): Promise<void>;
|
): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1307,10 +1307,11 @@ export interface ExtensionAPI {
|
||||||
/**
|
/**
|
||||||
* Send a user message to the agent. Always triggers a turn.
|
* 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.
|
* 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(
|
sendUserMessage(
|
||||||
content: string | (TextContent | ImageContent)[],
|
content: string | (TextContent | ImageContent)[],
|
||||||
options?: { deliverAs?: "steer" | "followUp" },
|
options?: { deliverAs?: "steer" | "followUp"; expandPromptTemplates?: boolean },
|
||||||
): void;
|
): void;
|
||||||
|
|
||||||
/** Append a custom entry to the session for state persistence (not sent to LLM). */
|
/** 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 = (
|
export type SendUserMessageHandler = (
|
||||||
content: string | (TextContent | ImageContent)[],
|
content: string | (TextContent | ImageContent)[],
|
||||||
options?: { deliverAs?: "steer" | "followUp" },
|
options?: { deliverAs?: "steer" | "followUp"; expandPromptTemplates?: boolean },
|
||||||
) => void;
|
) => void;
|
||||||
|
|
||||||
export type AppendEntryHandler = <T = unknown>(customType: string, data?: T) => 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 { fauxAssistantMessage, fauxToolCall, type Model } from "@earendil-works/pi-ai";
|
||||||
import { Type } from "typebox";
|
import { Type } from "typebox";
|
||||||
import { afterEach, describe, expect, it } from "vitest";
|
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 type { PromptTemplate } from "../../src/core/prompt-templates.ts";
|
||||||
import { createSyntheticSourceInfo } from "../../src/core/source-info.ts";
|
import { createSyntheticSourceInfo } from "../../src/core/source-info.ts";
|
||||||
import { createTestResourceLoader } from "../utilities.ts";
|
import { createTestResourceLoader } from "../utilities.ts";
|
||||||
|
|
@ -224,6 +224,39 @@ describe("AgentSession prompt characterization", () => {
|
||||||
expect(expandedPrompt).toBe("Review this code: src/index.ts");
|
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 () => {
|
it("dispatches extension commands without consuming a provider response", async () => {
|
||||||
const commandRuns: string[] = [];
|
const commandRuns: string[] = [];
|
||||||
const harness = await createHarness({
|
const harness = await createHarness({
|
||||||
|
|
@ -248,6 +281,35 @@ describe("AgentSession prompt characterization", () => {
|
||||||
expect(harness.getPendingResponseCount()).toBe(1);
|
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 () => {
|
it("sendUserMessage while idle triggers a turn", async () => {
|
||||||
const harness = await createHarness();
|
const harness = await createHarness();
|
||||||
harnesses.push(harness);
|
harnesses.push(harness);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue