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.images - attached images (if any)
|
||||
// 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 {
|
||||
// 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
|
||||
|
||||
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")` |
|
||||
| `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")` |
|
||||
| `prompt-customizer.ts` | Add context-aware tool guidance using `systemPromptOptions` | `on("before_agent_start")`, `BuildSystemPromptOptions` |
|
||||
| `file-trigger.ts` | File watcher triggers messages | `sendMessage` |
|
||||
| **Compaction & Sessions** |||
|
||||
| `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 { SlashCommandInfo } from "./slash-commands.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 { createAllToolDefinitions } from "./tools/index.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
|
||||
private _baseSystemPrompt = "";
|
||||
private _baseSystemPromptOptions: BuildSystemPromptOptions = {};
|
||||
|
||||
constructor(config: AgentSessionConfig) {
|
||||
this.agent = config.agent;
|
||||
@@ -906,7 +907,7 @@ export class AgentSession {
|
||||
const loadedSkills = this._resourceLoader.getSkills().skills;
|
||||
const loadedContextFiles = this._resourceLoader.getAgentsFiles().agentsFiles;
|
||||
|
||||
return buildSystemPrompt({
|
||||
this._baseSystemPromptOptions = {
|
||||
cwd: this._cwd,
|
||||
skills: loadedSkills,
|
||||
contextFiles: loadedContextFiles,
|
||||
@@ -915,7 +916,8 @@ export class AgentSession {
|
||||
selectedTools: validToolNames,
|
||||
toolSnippets,
|
||||
promptGuidelines,
|
||||
});
|
||||
};
|
||||
return buildSystemPrompt(this._baseSystemPromptOptions);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
@@ -1048,6 +1050,7 @@ export class AgentSession {
|
||||
expandedText,
|
||||
currentImages,
|
||||
this._baseSystemPrompt,
|
||||
this._baseSystemPromptOptions,
|
||||
);
|
||||
// Add all custom messages from extensions
|
||||
if (result?.messages) {
|
||||
|
||||
@@ -36,6 +36,7 @@ export type {
|
||||
BeforeAgentStartEventResult,
|
||||
BeforeProviderRequestEvent,
|
||||
BeforeProviderRequestEventResult,
|
||||
BuildSystemPromptOptions,
|
||||
// Context
|
||||
CompactOptions,
|
||||
// Events - Agent
|
||||
|
||||
@@ -10,6 +10,7 @@ import type { ResourceDiagnostic } from "../diagnostics.js";
|
||||
import type { KeybindingsConfig } from "../keybindings.js";
|
||||
import type { ModelRegistry } from "../model-registry.js";
|
||||
import type { SessionManager } from "../session-manager.js";
|
||||
import type { BuildSystemPromptOptions } from "../system-prompt.js";
|
||||
import type {
|
||||
BeforeAgentStartEvent,
|
||||
BeforeAgentStartEventResult,
|
||||
@@ -789,6 +790,7 @@ export class ExtensionRunner {
|
||||
prompt: string,
|
||||
images: ImageContent[] | undefined,
|
||||
systemPrompt: string,
|
||||
systemPromptOptions: BuildSystemPromptOptions,
|
||||
): Promise<BeforeAgentStartCombinedResult | undefined> {
|
||||
const ctx = this.createContext();
|
||||
const messages: NonNullable<BeforeAgentStartEventResult["message"]>[] = [];
|
||||
@@ -806,6 +808,7 @@ export class ExtensionRunner {
|
||||
prompt,
|
||||
images,
|
||||
systemPrompt: currentSystemPrompt,
|
||||
systemPromptOptions,
|
||||
};
|
||||
const handlerResult = await handler(event, ctx);
|
||||
|
||||
|
||||
@@ -57,6 +57,7 @@ import type {
|
||||
} from "../session-manager.js";
|
||||
import type { SlashCommandInfo } from "../slash-commands.js";
|
||||
import type { SourceInfo } from "../source-info.js";
|
||||
import type { BuildSystemPromptOptions } from "../system-prompt.js";
|
||||
import type { BashOperations } from "../tools/bash.js";
|
||||
import type { EditToolDetails } from "../tools/edit.js";
|
||||
import type {
|
||||
@@ -75,6 +76,7 @@ import type {
|
||||
} from "../tools/index.js";
|
||||
|
||||
export type { ExecOptions, ExecResult } from "../exec.js";
|
||||
export type { BuildSystemPromptOptions } from "../system-prompt.js";
|
||||
export type { AgentToolResult, AgentToolUpdateCallback, ToolExecutionMode };
|
||||
export type { AppKeybinding, KeybindingsManager } from "../keybindings.js";
|
||||
|
||||
@@ -582,9 +584,14 @@ export interface AfterProviderResponseEvent {
|
||||
/** Fired after user submits prompt but before agent loop. */
|
||||
export interface BeforeAgentStartEvent {
|
||||
type: "before_agent_start";
|
||||
/** The raw user prompt text (after expansion). */
|
||||
prompt: string;
|
||||
/** Images attached to the user prompt, if any. */
|
||||
images?: ImageContent[];
|
||||
/** The fully assembled system prompt 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 */
|
||||
|
||||
@@ -35,6 +35,8 @@ export {
|
||||
type AgentToolResult,
|
||||
type AgentToolUpdateCallback,
|
||||
type BeforeAgentStartEvent,
|
||||
type BeforeAgentStartEventResult,
|
||||
type BuildSystemPromptOptions,
|
||||
type ContextEvent,
|
||||
defineTool,
|
||||
discoverAndLoadExtensions,
|
||||
|
||||
@@ -56,8 +56,10 @@ export type {
|
||||
AppKeybinding,
|
||||
BashToolCallEvent,
|
||||
BeforeAgentStartEvent,
|
||||
BeforeAgentStartEventResult,
|
||||
BeforeProviderRequestEvent,
|
||||
BeforeProviderRequestEventResult,
|
||||
BuildSystemPromptOptions,
|
||||
CompactOptions,
|
||||
ContextEvent,
|
||||
ContextUsage,
|
||||
|
||||
@@ -21,6 +21,7 @@ import { AuthStorage } from "../src/core/auth-storage.js";
|
||||
import { ModelRegistry } from "../src/core/model-registry.js";
|
||||
import { SessionManager } from "../src/core/session-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";
|
||||
|
||||
// Mock stream that mimics AssistantMessageEventStream
|
||||
@@ -443,7 +444,12 @@ describe("AgentSession concurrent prompt guard", () => {
|
||||
images: unknown,
|
||||
source: "interactive" | "rpc" | "extension",
|
||||
) => 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 = {
|
||||
@@ -578,7 +584,12 @@ describe("AgentSession concurrent prompt guard", () => {
|
||||
images: unknown,
|
||||
source: "interactive" | "rpc" | "extension",
|
||||
) => 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 = {
|
||||
|
||||
Reference in New Issue
Block a user