feat: Expose BuildSystemPromptOptions on before-agent-start event. (#3473)
Resolves: #3463
This commit is contained in:
@@ -465,6 +465,15 @@ pi.on("before_agent_start", async (event, ctx) => {
|
|||||||
// event.prompt - user's prompt text
|
// event.prompt - user's prompt text
|
||||||
// event.images - attached images (if any)
|
// event.images - attached images (if any)
|
||||||
// event.systemPrompt - current system prompt
|
// event.systemPrompt - current system prompt
|
||||||
|
// event.systemPromptOptions - structured options used to build the system prompt
|
||||||
|
// .customPrompt - any custom system prompt (from --system-prompt, SYSTEM.md, or custom templates)
|
||||||
|
// .selectedTools - tools currently active in the prompt
|
||||||
|
// .toolSnippets - one-line descriptions for each tool
|
||||||
|
// .promptGuidelines - custom guideline bullets
|
||||||
|
// .appendSystemPrompt - text from --append-system-prompt flags
|
||||||
|
// .cwd - working directory
|
||||||
|
// .contextFiles - AGENTS.md files and other loaded context files
|
||||||
|
// .skills - loaded skills
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// Inject a persistent message (stored in session, sent to LLM)
|
// Inject a persistent message (stored in session, sent to LLM)
|
||||||
@@ -479,6 +488,8 @@ pi.on("before_agent_start", async (event, ctx) => {
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The `systemPromptOptions` field gives extensions access to the same structured data Pi uses to build the system prompt. This lets you inspect what Pi has loaded — custom prompts, guidelines, tool snippets, context files, skills — without re-discovering resources or re-parsing flags. Use it when your extension needs to make deep, informed changes to the system prompt while respecting user-provided configuration.
|
||||||
|
|
||||||
#### agent_start / agent_end
|
#### agent_start / agent_end
|
||||||
|
|
||||||
Fired once per user prompt.
|
Fired once per user prompt.
|
||||||
@@ -2320,6 +2331,7 @@ All examples in [examples/extensions/](../examples/extensions/).
|
|||||||
| `provider-payload.ts` | Inspect payloads and provider response headers | `on("before_provider_request")`, `on("after_provider_response")` |
|
| `provider-payload.ts` | Inspect payloads and provider response headers | `on("before_provider_request")`, `on("after_provider_response")` |
|
||||||
| `system-prompt-header.ts` | Display system prompt info | `on("agent_start")`, `getSystemPrompt` |
|
| `system-prompt-header.ts` | Display system prompt info | `on("agent_start")`, `getSystemPrompt` |
|
||||||
| `claude-rules.ts` | Load rules from files | `on("session_start")`, `on("before_agent_start")` |
|
| `claude-rules.ts` | Load rules from files | `on("session_start")`, `on("before_agent_start")` |
|
||||||
|
| `prompt-customizer.ts` | Add context-aware tool guidance using `systemPromptOptions` | `on("before_agent_start")`, `BuildSystemPromptOptions` |
|
||||||
| `file-trigger.ts` | File watcher triggers messages | `sendMessage` |
|
| `file-trigger.ts` | File watcher triggers messages | `sendMessage` |
|
||||||
| **Compaction & Sessions** |||
|
| **Compaction & Sessions** |||
|
||||||
| `custom-compaction.ts` | Custom compaction summary | `on("session_before_compact")` |
|
| `custom-compaction.ts` | Custom compaction summary | `on("session_before_compact")` |
|
||||||
|
|||||||
@@ -0,0 +1,97 @@
|
|||||||
|
/**
|
||||||
|
* Prompt Customizer Extension
|
||||||
|
*
|
||||||
|
* Demonstrates using systemPromptOptions to make informed, context-aware
|
||||||
|
* modifications to the system prompt without re-discovering resources.
|
||||||
|
*
|
||||||
|
* This extension adds tool-specific guidance based on what tools and skills
|
||||||
|
* are currently active, respecting whatever the user has configured.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* 1. Copy this file to ~/.pi/agent/extensions/ or your project's .pi/extensions/
|
||||||
|
* 2. Use the extension — it automatically adapts to your active tools and skills
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { BuildSystemPromptOptions, ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds tool-specific guidance that adapts to the active tool set.
|
||||||
|
* Instead of appending one-size-fits-all instructions, this reads what's
|
||||||
|
* actually loaded and tailors the guidance accordingly.
|
||||||
|
*/
|
||||||
|
function addToolGuidance(options: BuildSystemPromptOptions, basePrompt: string): string {
|
||||||
|
const hasTool = (name: string) => options.selectedTools?.includes(name) ?? false;
|
||||||
|
|
||||||
|
const parts: string[] = [];
|
||||||
|
|
||||||
|
if (hasTool("read")) {
|
||||||
|
parts.push(
|
||||||
|
"• Use the `read` tool for file contents (supports text and images).",
|
||||||
|
" - For large files, use `offset` and `limit` to read in chunks.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasTool("bash")) {
|
||||||
|
parts.push("• Execute commands with the `bash` tool. Use it for file operations like `ls`, `find`, `grep`.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasTool("edit")) {
|
||||||
|
parts.push(
|
||||||
|
"• Use the `edit` tool for precise text replacements in files. Match exact content including whitespace.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasTool("write")) {
|
||||||
|
parts.push("• Use the `write` tool to create new files or overwrite existing ones completely.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.skills && options.skills.length > 0) {
|
||||||
|
const skillNames = options.skills.map((s) => s.name).join(", ");
|
||||||
|
parts.push(`\nAvailable skills: ${skillNames}`, "Use skill documentation for best practices on specific tools.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parts.length === 0) {
|
||||||
|
return basePrompt;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${basePrompt}
|
||||||
|
|
||||||
|
## Tool Guidance
|
||||||
|
|
||||||
|
${parts.join("\n")}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merges extension instructions with user-provided append prompts.
|
||||||
|
* This respects whatever the user configured via --append-system-prompt
|
||||||
|
* flags or files, rather than duplicating that work.
|
||||||
|
*/
|
||||||
|
function mergeWithUserAppend(options: BuildSystemPromptOptions): string {
|
||||||
|
const userAppend = options.appendSystemPrompt;
|
||||||
|
const extensionSpecific = `
|
||||||
|
## Extension-Added Context
|
||||||
|
|
||||||
|
This prompt includes tool guidance and skill information loaded dynamically.
|
||||||
|
If you have additional requirements, configure them via --append-system-prompt or project context files.
|
||||||
|
`;
|
||||||
|
|
||||||
|
if (userAppend) {
|
||||||
|
return `${userAppend}\n\n${extensionSpecific}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return extensionSpecific;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function promptCustomizer(pi: ExtensionAPI) {
|
||||||
|
pi.on("before_agent_start", async (event) => {
|
||||||
|
const { systemPrompt, systemPromptOptions } = event;
|
||||||
|
|
||||||
|
const customPrompt = addToolGuidance(systemPromptOptions, systemPrompt);
|
||||||
|
const appendSection = mergeWithUserAppend(systemPromptOptions);
|
||||||
|
|
||||||
|
return {
|
||||||
|
systemPrompt: `${customPrompt}${appendSection}`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -76,7 +76,7 @@ import { CURRENT_SESSION_VERSION, getLatestCompactionEntry, type SessionHeader }
|
|||||||
import type { SettingsManager } from "./settings-manager.js";
|
import type { SettingsManager } from "./settings-manager.js";
|
||||||
import type { SlashCommandInfo } from "./slash-commands.js";
|
import type { SlashCommandInfo } from "./slash-commands.js";
|
||||||
import { createSyntheticSourceInfo, type SourceInfo } from "./source-info.js";
|
import { createSyntheticSourceInfo, type SourceInfo } from "./source-info.js";
|
||||||
import { buildSystemPrompt } from "./system-prompt.js";
|
import { type BuildSystemPromptOptions, buildSystemPrompt } from "./system-prompt.js";
|
||||||
import { type BashOperations, createLocalBashOperations } from "./tools/bash.js";
|
import { type BashOperations, createLocalBashOperations } from "./tools/bash.js";
|
||||||
import { createAllToolDefinitions } from "./tools/index.js";
|
import { createAllToolDefinitions } from "./tools/index.js";
|
||||||
import { createToolDefinitionFromAgentTool } from "./tools/tool-definition-wrapper.js";
|
import { createToolDefinitionFromAgentTool } from "./tools/tool-definition-wrapper.js";
|
||||||
@@ -300,6 +300,7 @@ export class AgentSession {
|
|||||||
|
|
||||||
// Base system prompt (without extension appends) - used to apply fresh appends each turn
|
// Base system prompt (without extension appends) - used to apply fresh appends each turn
|
||||||
private _baseSystemPrompt = "";
|
private _baseSystemPrompt = "";
|
||||||
|
private _baseSystemPromptOptions: BuildSystemPromptOptions = {};
|
||||||
|
|
||||||
constructor(config: AgentSessionConfig) {
|
constructor(config: AgentSessionConfig) {
|
||||||
this.agent = config.agent;
|
this.agent = config.agent;
|
||||||
@@ -906,7 +907,7 @@ export class AgentSession {
|
|||||||
const loadedSkills = this._resourceLoader.getSkills().skills;
|
const loadedSkills = this._resourceLoader.getSkills().skills;
|
||||||
const loadedContextFiles = this._resourceLoader.getAgentsFiles().agentsFiles;
|
const loadedContextFiles = this._resourceLoader.getAgentsFiles().agentsFiles;
|
||||||
|
|
||||||
return buildSystemPrompt({
|
this._baseSystemPromptOptions = {
|
||||||
cwd: this._cwd,
|
cwd: this._cwd,
|
||||||
skills: loadedSkills,
|
skills: loadedSkills,
|
||||||
contextFiles: loadedContextFiles,
|
contextFiles: loadedContextFiles,
|
||||||
@@ -915,7 +916,8 @@ export class AgentSession {
|
|||||||
selectedTools: validToolNames,
|
selectedTools: validToolNames,
|
||||||
toolSnippets,
|
toolSnippets,
|
||||||
promptGuidelines,
|
promptGuidelines,
|
||||||
});
|
};
|
||||||
|
return buildSystemPrompt(this._baseSystemPromptOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
@@ -1048,6 +1050,7 @@ export class AgentSession {
|
|||||||
expandedText,
|
expandedText,
|
||||||
currentImages,
|
currentImages,
|
||||||
this._baseSystemPrompt,
|
this._baseSystemPrompt,
|
||||||
|
this._baseSystemPromptOptions,
|
||||||
);
|
);
|
||||||
// Add all custom messages from extensions
|
// Add all custom messages from extensions
|
||||||
if (result?.messages) {
|
if (result?.messages) {
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ export type {
|
|||||||
BeforeAgentStartEventResult,
|
BeforeAgentStartEventResult,
|
||||||
BeforeProviderRequestEvent,
|
BeforeProviderRequestEvent,
|
||||||
BeforeProviderRequestEventResult,
|
BeforeProviderRequestEventResult,
|
||||||
|
BuildSystemPromptOptions,
|
||||||
// Context
|
// Context
|
||||||
CompactOptions,
|
CompactOptions,
|
||||||
// Events - Agent
|
// Events - Agent
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import type { ResourceDiagnostic } from "../diagnostics.js";
|
|||||||
import type { KeybindingsConfig } from "../keybindings.js";
|
import type { KeybindingsConfig } from "../keybindings.js";
|
||||||
import type { ModelRegistry } from "../model-registry.js";
|
import type { ModelRegistry } from "../model-registry.js";
|
||||||
import type { SessionManager } from "../session-manager.js";
|
import type { SessionManager } from "../session-manager.js";
|
||||||
|
import type { BuildSystemPromptOptions } from "../system-prompt.js";
|
||||||
import type {
|
import type {
|
||||||
BeforeAgentStartEvent,
|
BeforeAgentStartEvent,
|
||||||
BeforeAgentStartEventResult,
|
BeforeAgentStartEventResult,
|
||||||
@@ -789,6 +790,7 @@ export class ExtensionRunner {
|
|||||||
prompt: string,
|
prompt: string,
|
||||||
images: ImageContent[] | undefined,
|
images: ImageContent[] | undefined,
|
||||||
systemPrompt: string,
|
systemPrompt: string,
|
||||||
|
systemPromptOptions: BuildSystemPromptOptions,
|
||||||
): Promise<BeforeAgentStartCombinedResult | undefined> {
|
): Promise<BeforeAgentStartCombinedResult | undefined> {
|
||||||
const ctx = this.createContext();
|
const ctx = this.createContext();
|
||||||
const messages: NonNullable<BeforeAgentStartEventResult["message"]>[] = [];
|
const messages: NonNullable<BeforeAgentStartEventResult["message"]>[] = [];
|
||||||
@@ -806,6 +808,7 @@ export class ExtensionRunner {
|
|||||||
prompt,
|
prompt,
|
||||||
images,
|
images,
|
||||||
systemPrompt: currentSystemPrompt,
|
systemPrompt: currentSystemPrompt,
|
||||||
|
systemPromptOptions,
|
||||||
};
|
};
|
||||||
const handlerResult = await handler(event, ctx);
|
const handlerResult = await handler(event, ctx);
|
||||||
|
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ import type {
|
|||||||
} from "../session-manager.js";
|
} from "../session-manager.js";
|
||||||
import type { SlashCommandInfo } from "../slash-commands.js";
|
import type { SlashCommandInfo } from "../slash-commands.js";
|
||||||
import type { SourceInfo } from "../source-info.js";
|
import type { SourceInfo } from "../source-info.js";
|
||||||
|
import type { BuildSystemPromptOptions } from "../system-prompt.js";
|
||||||
import type { BashOperations } from "../tools/bash.js";
|
import type { BashOperations } from "../tools/bash.js";
|
||||||
import type { EditToolDetails } from "../tools/edit.js";
|
import type { EditToolDetails } from "../tools/edit.js";
|
||||||
import type {
|
import type {
|
||||||
@@ -75,6 +76,7 @@ import type {
|
|||||||
} from "../tools/index.js";
|
} from "../tools/index.js";
|
||||||
|
|
||||||
export type { ExecOptions, ExecResult } from "../exec.js";
|
export type { ExecOptions, ExecResult } from "../exec.js";
|
||||||
|
export type { BuildSystemPromptOptions } from "../system-prompt.js";
|
||||||
export type { AgentToolResult, AgentToolUpdateCallback, ToolExecutionMode };
|
export type { AgentToolResult, AgentToolUpdateCallback, ToolExecutionMode };
|
||||||
export type { AppKeybinding, KeybindingsManager } from "../keybindings.js";
|
export type { AppKeybinding, KeybindingsManager } from "../keybindings.js";
|
||||||
|
|
||||||
@@ -582,9 +584,14 @@ export interface AfterProviderResponseEvent {
|
|||||||
/** Fired after user submits prompt but before agent loop. */
|
/** Fired after user submits prompt but before agent loop. */
|
||||||
export interface BeforeAgentStartEvent {
|
export interface BeforeAgentStartEvent {
|
||||||
type: "before_agent_start";
|
type: "before_agent_start";
|
||||||
|
/** The raw user prompt text (after expansion). */
|
||||||
prompt: string;
|
prompt: string;
|
||||||
|
/** Images attached to the user prompt, if any. */
|
||||||
images?: ImageContent[];
|
images?: ImageContent[];
|
||||||
|
/** The fully assembled system prompt string. */
|
||||||
systemPrompt: string;
|
systemPrompt: string;
|
||||||
|
/** Structured options used to build the system prompt. Extensions can inspect this to understand what Pi loaded without re-discovering resources. */
|
||||||
|
systemPromptOptions: BuildSystemPromptOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Fired when an agent loop starts */
|
/** Fired when an agent loop starts */
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ export {
|
|||||||
type AgentToolResult,
|
type AgentToolResult,
|
||||||
type AgentToolUpdateCallback,
|
type AgentToolUpdateCallback,
|
||||||
type BeforeAgentStartEvent,
|
type BeforeAgentStartEvent,
|
||||||
|
type BeforeAgentStartEventResult,
|
||||||
|
type BuildSystemPromptOptions,
|
||||||
type ContextEvent,
|
type ContextEvent,
|
||||||
defineTool,
|
defineTool,
|
||||||
discoverAndLoadExtensions,
|
discoverAndLoadExtensions,
|
||||||
|
|||||||
@@ -56,8 +56,10 @@ export type {
|
|||||||
AppKeybinding,
|
AppKeybinding,
|
||||||
BashToolCallEvent,
|
BashToolCallEvent,
|
||||||
BeforeAgentStartEvent,
|
BeforeAgentStartEvent,
|
||||||
|
BeforeAgentStartEventResult,
|
||||||
BeforeProviderRequestEvent,
|
BeforeProviderRequestEvent,
|
||||||
BeforeProviderRequestEventResult,
|
BeforeProviderRequestEventResult,
|
||||||
|
BuildSystemPromptOptions,
|
||||||
CompactOptions,
|
CompactOptions,
|
||||||
ContextEvent,
|
ContextEvent,
|
||||||
ContextUsage,
|
ContextUsage,
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import { AuthStorage } from "../src/core/auth-storage.js";
|
|||||||
import { ModelRegistry } from "../src/core/model-registry.js";
|
import { ModelRegistry } from "../src/core/model-registry.js";
|
||||||
import { SessionManager } from "../src/core/session-manager.js";
|
import { SessionManager } from "../src/core/session-manager.js";
|
||||||
import { SettingsManager } from "../src/core/settings-manager.js";
|
import { SettingsManager } from "../src/core/settings-manager.js";
|
||||||
|
import type { BuildSystemPromptOptions } from "../src/core/system-prompt.js";
|
||||||
import { createTestExtensionsResult, createTestResourceLoader } from "./utilities.js";
|
import { createTestExtensionsResult, createTestResourceLoader } from "./utilities.js";
|
||||||
|
|
||||||
// Mock stream that mimics AssistantMessageEventStream
|
// Mock stream that mimics AssistantMessageEventStream
|
||||||
@@ -443,7 +444,12 @@ describe("AgentSession concurrent prompt guard", () => {
|
|||||||
images: unknown,
|
images: unknown,
|
||||||
source: "interactive" | "rpc" | "extension",
|
source: "interactive" | "rpc" | "extension",
|
||||||
) => Promise<{ action: "continue" }>;
|
) => Promise<{ action: "continue" }>;
|
||||||
emitBeforeAgentStart: (prompt: string, images: unknown, systemPrompt: string) => Promise<undefined>;
|
emitBeforeAgentStart: (
|
||||||
|
prompt: string,
|
||||||
|
images: unknown,
|
||||||
|
systemPrompt: string,
|
||||||
|
systemPromptOptions: BuildSystemPromptOptions,
|
||||||
|
) => Promise<undefined>;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
sessionWithRunner._extensionRunner = {
|
sessionWithRunner._extensionRunner = {
|
||||||
@@ -578,7 +584,12 @@ describe("AgentSession concurrent prompt guard", () => {
|
|||||||
images: unknown,
|
images: unknown,
|
||||||
source: "interactive" | "rpc" | "extension",
|
source: "interactive" | "rpc" | "extension",
|
||||||
) => Promise<{ action: "continue" }>;
|
) => Promise<{ action: "continue" }>;
|
||||||
emitBeforeAgentStart: (prompt: string, images: unknown, systemPrompt: string) => Promise<undefined>;
|
emitBeforeAgentStart: (
|
||||||
|
prompt: string,
|
||||||
|
images: unknown,
|
||||||
|
systemPrompt: string,
|
||||||
|
systemPromptOptions: BuildSystemPromptOptions,
|
||||||
|
) => Promise<undefined>;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
sessionWithRunner._extensionRunner = {
|
sessionWithRunner._extensionRunner = {
|
||||||
|
|||||||
Reference in New Issue
Block a user