fix(coding-agent): make prompt snippets opt in closes #2285

This commit is contained in:
Mario Zechner
2026-03-17 12:44:16 +01:00
parent 8e3bb4ff55
commit 7817e9b227
6 changed files with 67 additions and 6 deletions

View File

@@ -2,6 +2,10 @@
## [Unreleased]
### Breaking Changes
- Changed custom tool system prompt behavior so extension and SDK tools are included in the default `Available tools` section only when they provide `promptSnippet`. Omitting `promptSnippet` now leaves the tool out of that section instead of falling back to `description` ([#2285](https://github.com/badlogic/pi-mono/issues/2285))
## [0.58.4] - 2026-03-16
### Fixed

View File

@@ -934,7 +934,7 @@ Register a custom tool callable by the LLM. See [Custom Tools](#custom-tools) fo
Use `pi.setActiveTools()` to enable or disable tools (including dynamically added tools) at runtime.
Use `promptSnippet` to customize that tool's one-line entry in `Available tools`, and `promptGuidelines` to append tool-specific bullets to the default `Guidelines` section when the tool is active.
Use `promptSnippet` to opt a custom tool into a one-line entry in `Available tools`, and `promptGuidelines` to append tool-specific bullets to the default `Guidelines` section when the tool is active.
See [dynamic-tools.ts](../examples/extensions/dynamic-tools.ts) for a full example.
@@ -1336,7 +1336,7 @@ export default function (pi: ExtensionAPI) {
Register tools the LLM can call via `pi.registerTool()`. Tools appear in the system prompt and can have custom rendering.
Use `promptSnippet` for a short one-line entry in the `Available tools` section in the default system prompt. If omitted, pi falls back to `description`.
Use `promptSnippet` for a short one-line entry in the `Available tools` section in the default system prompt. If omitted, custom tools are left out of that section.
Use `promptGuidelines` to add tool-specific bullets to the default system prompt `Guidelines` section. These bullets are included only while the tool is active (for example, after `pi.setActiveTools([...])`).

View File

@@ -339,7 +339,7 @@ export interface ToolDefinition<TParams extends TSchema = TSchema, TDetails = un
label: string;
/** Description for LLM */
description: string;
/** Optional one-line snippet for the Available tools section in the default system prompt. Falls back to description when omitted. */
/** Optional one-line snippet for the Available tools section in the default system prompt. Custom tools are omitted from that section when this is not provided. */
promptSnippet?: string;
/** Optional guideline bullets appended to the default system prompt Guidelines section when this tool is active. */
promptGuidelines?: string[];

View File

@@ -94,9 +94,10 @@ export function buildSystemPrompt(options: BuildSystemPromptOptions = {}): strin
// Build tools list based on selected tools.
// Built-ins use toolDescriptions. Custom tools can provide one-line snippets.
const tools = selectedTools || ["read", "bash", "edit", "write"];
const visibleTools = tools.filter((name) => name in toolDescriptions || toolSnippets?.[name]);
const toolsList =
tools.length > 0
? tools
visibleTools.length > 0
? visibleTools
.map((name) => {
const snippet = toolSnippets?.[name] ?? toolDescriptions[name] ?? name;
return `- ${name}: ${snippet}`;

View File

@@ -74,4 +74,50 @@ describe("AgentSession dynamic tool registration", () => {
session.dispose();
});
it("keeps custom tools active but omits them from available tools when promptSnippet is not provided", async () => {
const settingsManager = SettingsManager.create(tempDir, agentDir);
const sessionManager = SessionManager.inMemory();
const resourceLoader = new DefaultResourceLoader({
cwd: tempDir,
agentDir,
settingsManager,
extensionFactories: [
(pi) => {
pi.on("session_start", () => {
pi.registerTool({
name: "hidden_tool",
label: "Hidden Tool",
description: "Description should not appear in available tools",
parameters: Type.Object({}),
execute: async () => ({
content: [{ type: "text", text: "ok" }],
details: {},
}),
});
});
},
],
});
await resourceLoader.reload();
const { session } = await createAgentSession({
cwd: tempDir,
agentDir,
model: getModel("anthropic", "claude-sonnet-4-5")!,
settingsManager,
sessionManager,
resourceLoader,
});
await session.bindExtensions({});
expect(session.getAllTools().map((tool) => tool.name)).toContain("hidden_tool");
expect(session.getActiveToolNames()).toContain("hidden_tool");
expect(session.systemPrompt).not.toContain("hidden_tool");
expect(session.systemPrompt).not.toContain("Description should not appear in available tools");
session.dispose();
});
});

View File

@@ -39,7 +39,7 @@ describe("buildSystemPrompt", () => {
});
describe("custom tool snippets", () => {
test("includes custom tools in available tools section", () => {
test("includes custom tools in available tools section when promptSnippet is provided", () => {
const prompt = buildSystemPrompt({
selectedTools: ["read", "dynamic_tool"],
toolSnippets: {
@@ -51,6 +51,16 @@ describe("buildSystemPrompt", () => {
expect(prompt).toContain("- dynamic_tool: Run dynamic test behavior");
});
test("omits custom tools from available tools section when promptSnippet is not provided", () => {
const prompt = buildSystemPrompt({
selectedTools: ["read", "dynamic_tool"],
contextFiles: [],
skills: [],
});
expect(prompt).not.toContain("dynamic_tool");
});
});
describe("prompt guidelines", () => {