Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions packages/coding-agent/src/core/tools/bash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ const bashSchema = Type.Object({
timeout: Type.Optional(Type.Number({ description: "Timeout in seconds (optional, no default timeout)" })),
});

export const bashToolSystemPromptContribution = {
snippet: "Execute bash commands (ls, grep, find, etc.)",
guidelines: ["Inspect PI_* environment variables for current model and session details."],
} as const;

export type BashToolInput = Static<typeof bashSchema>;

export interface BashToolDetails {
Expand Down Expand Up @@ -325,10 +330,8 @@ export function createBashToolDefinition(
name: "bash",
label: "bash",
description: `Execute a bash command in the current working directory. Returns stdout and stderr. Output is truncated to last ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). If truncated, full output is saved to a temp file. Optionally provide a timeout in seconds.`,
promptSnippet: "Execute bash commands (ls, grep, find, etc.)",
promptGuidelines: exposeSessionEnvironment
? ["Inspect PI_* environment variables for current model and session details."]
: undefined,
promptSnippet: bashToolSystemPromptContribution.snippet,
promptGuidelines: exposeSessionEnvironment ? [...bashToolSystemPromptContribution.guidelines] : undefined,
parameters: bashSchema,
async execute(
_toolCallId,
Expand Down
20 changes: 12 additions & 8 deletions packages/coding-agent/src/core/tools/edit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,16 @@ const editSchema = Type.Object(
{},
);

export const editToolSystemPromptContribution = {
snippet: "Make precise file edits with exact text replacement, including multiple disjoint edits in one call",
guidelines: [
"Use edit for precise changes (edits[].oldText must match exactly)",
"When changing multiple separate locations in one file, use one edit call with multiple entries in edits[] instead of multiple edit calls",
"Each edits[].oldText is matched against the original file, not after earlier edits are applied. Do not emit overlapping or nested edits. Merge nearby changes into one edit.",
"Keep edits[].oldText as small as possible while still being unique in the file. Do not pad with large unchanged regions.",
],
} as const;

export type EditToolInput = Static<typeof editSchema>;
type LegacyEditToolInput = EditToolInput & {
oldText?: unknown;
Expand Down Expand Up @@ -294,14 +304,8 @@ export function createEditToolDefinition(
label: "edit",
description:
"Edit a single file using exact text replacement. Every edits[].oldText must match a unique, non-overlapping region of the original file. If two changes affect the same block or nearby lines, merge them into one edit instead of emitting overlapping edits. Do not include large unchanged regions just to connect distant changes.",
promptSnippet:
"Make precise file edits with exact text replacement, including multiple disjoint edits in one call",
promptGuidelines: [
"Use edit for precise changes (edits[].oldText must match exactly)",
"When changing multiple separate locations in one file, use one edit call with multiple entries in edits[] instead of multiple edit calls",
"Each edits[].oldText is matched against the original file, not after earlier edits are applied. Do not emit overlapping or nested edits. Merge nearby changes into one edit.",
"Keep edits[].oldText as small as possible while still being unique in the file. Do not pad with large unchanged regions.",
],
promptSnippet: editToolSystemPromptContribution.snippet,
promptGuidelines: [...editToolSystemPromptContribution.guidelines],
parameters: editSchema,
renderShell: "self",
prepareArguments: prepareEditArguments,
Expand Down
7 changes: 6 additions & 1 deletion packages/coding-agent/src/core/tools/find.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ const findSchema = Type.Object({
limit: Type.Optional(Type.Number({ description: "Maximum number of results (default: 1000)" })),
});

export const findToolSystemPromptContribution = {
snippet: "Find files by glob pattern (respects .gitignore)",
guidelines: [],
} as const;

export type FindToolInput = Static<typeof findSchema>;

const DEFAULT_LIMIT = 1000;
Expand Down Expand Up @@ -124,7 +129,7 @@ export function createFindToolDefinition(
name: "find",
label: "find",
description: `Search for files by glob pattern. Returns matching file paths relative to the search directory. Respects .gitignore. Output is truncated to ${DEFAULT_LIMIT} results or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first).`,
promptSnippet: "Find files by glob pattern (respects .gitignore)",
promptSnippet: findToolSystemPromptContribution.snippet,
parameters: findSchema,
async execute(
_toolCallId,
Expand Down
7 changes: 6 additions & 1 deletion packages/coding-agent/src/core/tools/grep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ const grepSchema = Type.Object({
limit: Type.Optional(Type.Number({ description: "Maximum number of matches to return (default: 100)" })),
});

export const grepToolSystemPromptContribution = {
snippet: "Search file contents for patterns (respects .gitignore)",
guidelines: [],
} as const;

export type GrepToolInput = Static<typeof grepSchema>;
const DEFAULT_LIMIT = 100;

Expand Down Expand Up @@ -129,7 +134,7 @@ export function createGrepToolDefinition(
name: "grep",
label: "grep",
description: `Search file contents for a pattern. Returns matching lines with file paths and line numbers. Respects .gitignore. Output is truncated to ${DEFAULT_LIMIT} matches or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). Long lines are truncated to ${GREP_MAX_LINE_LENGTH} chars.`,
promptSnippet: "Search file contents for patterns (respects .gitignore)",
promptSnippet: grepToolSystemPromptContribution.snippet,
parameters: grepSchema,
async execute(
_toolCallId,
Expand Down
7 changes: 6 additions & 1 deletion packages/coding-agent/src/core/tools/ls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ const lsSchema = Type.Object({
limit: Type.Optional(Type.Number({ description: "Maximum number of entries to return (default: 500)" })),
});

export const lsToolSystemPromptContribution = {
snippet: "List directory contents",
guidelines: [],
} as const;

export type LsToolInput = Static<typeof lsSchema>;

const DEFAULT_LIMIT = 500;
Expand Down Expand Up @@ -101,7 +106,7 @@ export function createLsToolDefinition(
name: "ls",
label: "ls",
description: `List directory contents. Returns entries sorted alphabetically, with '/' suffix for directories. Includes dotfiles. Output is truncated to ${DEFAULT_LIMIT} entries or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first).`,
promptSnippet: "List directory contents",
promptSnippet: lsToolSystemPromptContribution.snippet,
parameters: lsSchema,
async execute(
_toolCallId,
Expand Down
9 changes: 7 additions & 2 deletions packages/coding-agent/src/core/tools/read.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ const readSchema = Type.Object({
limit: Type.Optional(Type.Number({ description: "Maximum number of lines to read" })),
});

export const readToolSystemPromptContribution = {
snippet: "Read file contents",
guidelines: ["Use read to examine files instead of cat or sed."],
} as const;

export type ReadToolInput = Static<typeof readSchema>;

export interface ReadToolDetails {
Expand Down Expand Up @@ -210,8 +215,8 @@ export function createReadToolDefinition(
name: "read",
label: "read",
description: `Read the contents of a file. Supports text files and images (jpg, png, gif, webp, bmp). Images are sent as attachments. For text files, output is truncated to ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). Use offset/limit for large files. When you need the full file, continue with offset until complete.`,
promptSnippet: "Read file contents",
promptGuidelines: ["Use read to examine files instead of cat or sed."],
promptSnippet: readToolSystemPromptContribution.snippet,
promptGuidelines: [...readToolSystemPromptContribution.guidelines],
parameters: readSchema,
async execute(
_toolCallId,
Expand Down
9 changes: 7 additions & 2 deletions packages/coding-agent/src/core/tools/write.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ const writeSchema = Type.Object({
content: Type.String({ description: "Content to write to the file" }),
});

export const writeToolSystemPromptContribution = {
snippet: "Create or overwrite files",
guidelines: ["Use write only for new files or complete rewrites."],
} as const;

export type WriteToolInput = Static<typeof writeSchema>;

/**
Expand Down Expand Up @@ -188,8 +193,8 @@ export function createWriteToolDefinition(
label: "write",
description:
"Write content to a file. Creates the file if it doesn't exist, overwrites if it does. Automatically creates parent directories.",
promptSnippet: "Create or overwrite files",
promptGuidelines: ["Use write only for new files or complete rewrites."],
promptSnippet: writeToolSystemPromptContribution.snippet,
promptGuidelines: [...writeToolSystemPromptContribution.guidelines],
parameters: writeSchema,
async execute(
_toolCallId,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { describe, expect, test } from "vitest";
import { bashToolSystemPromptContribution, createBashToolDefinition } from "../src/core/tools/bash.ts";
import { createEditToolDefinition, editToolSystemPromptContribution } from "../src/core/tools/edit.ts";
import { createFindToolDefinition, findToolSystemPromptContribution } from "../src/core/tools/find.ts";
import { createGrepToolDefinition, grepToolSystemPromptContribution } from "../src/core/tools/grep.ts";
import { createLsToolDefinition, lsToolSystemPromptContribution } from "../src/core/tools/ls.ts";
import { createReadToolDefinition, readToolSystemPromptContribution } from "../src/core/tools/read.ts";
import { createWriteToolDefinition, writeToolSystemPromptContribution } from "../src/core/tools/write.ts";

const cases = [
["read", readToolSystemPromptContribution, createReadToolDefinition],
["bash", bashToolSystemPromptContribution, createBashToolDefinition],
["edit", editToolSystemPromptContribution, createEditToolDefinition],
["write", writeToolSystemPromptContribution, createWriteToolDefinition],
["grep", grepToolSystemPromptContribution, createGrepToolDefinition],
["find", findToolSystemPromptContribution, createFindToolDefinition],
["ls", lsToolSystemPromptContribution, createLsToolDefinition],
] as const;

describe("built-in tool system prompt contributions", () => {
test.each(cases)(
"keeps the %s tool definition aligned with its contribution",
(_name, contribution, createDefinition) => {
const definition = createDefinition("/workspace");

expect(definition.promptSnippet).toBe(contribution.snippet);
expect(definition.promptGuidelines ?? []).toEqual(contribution.guidelines);
},
);

test("keeps bash session-environment guidance conditional", () => {
const definition = createBashToolDefinition("/workspace", { exposeSessionEnvironment: false });

expect(definition.promptGuidelines).toBeUndefined();
});
});