mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-20 16:05:11 +00:00
Fix SDK tools to respect cwd option
Core tools now properly use the cwd passed to createAgentSession(). Added tool factory functions for SDK users who specify custom cwd with explicit tools. Fixes #279
This commit is contained in:
parent
42bc368e70
commit
face745f3d
16 changed files with 1243 additions and 1044 deletions
|
|
@ -2,6 +2,10 @@
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **SDK tools respect cwd**: Core tools (bash, read, edit, write, grep, find, ls) now properly use the `cwd` option from `createAgentSession()`. Added tool factory functions (`createBashTool`, `createReadTool`, etc.) for SDK users who specify custom `cwd` with explicit tools. ([#279](https://github.com/badlogic/pi-mono/issues/279))
|
||||||
|
|
||||||
## [0.26.0] - 2025-12-22
|
## [0.26.0] - 2025-12-22
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
||||||
|
|
@ -307,7 +307,7 @@ const { session } = await createAgentSession({
|
||||||
```typescript
|
```typescript
|
||||||
import {
|
import {
|
||||||
codingTools, // read, bash, edit, write (default)
|
codingTools, // read, bash, edit, write (default)
|
||||||
readOnlyTools, // read, bash
|
readOnlyTools, // read, grep, find, ls
|
||||||
readTool, bashTool, editTool, writeTool,
|
readTool, bashTool, editTool, writeTool,
|
||||||
grepTool, findTool, lsTool,
|
grepTool, findTool, lsTool,
|
||||||
} from "@mariozechner/pi-coding-agent";
|
} from "@mariozechner/pi-coding-agent";
|
||||||
|
|
@ -323,6 +323,45 @@ const { session } = await createAgentSession({
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Tools with Custom cwd
|
||||||
|
|
||||||
|
**Important:** The pre-built tool instances (`readTool`, `bashTool`, etc.) use `process.cwd()` for path resolution. When you specify a custom `cwd` AND provide explicit `tools`, you must use the tool factory functions to ensure paths resolve correctly:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import {
|
||||||
|
createCodingTools, // Creates [read, bash, edit, write] for specific cwd
|
||||||
|
createReadOnlyTools, // Creates [read, grep, find, ls] for specific cwd
|
||||||
|
createReadTool,
|
||||||
|
createBashTool,
|
||||||
|
createEditTool,
|
||||||
|
createWriteTool,
|
||||||
|
createGrepTool,
|
||||||
|
createFindTool,
|
||||||
|
createLsTool,
|
||||||
|
} from "@mariozechner/pi-coding-agent";
|
||||||
|
|
||||||
|
const cwd = "/path/to/project";
|
||||||
|
|
||||||
|
// Use factory for tool sets
|
||||||
|
const { session } = await createAgentSession({
|
||||||
|
cwd,
|
||||||
|
tools: createCodingTools(cwd), // Tools resolve paths relative to cwd
|
||||||
|
});
|
||||||
|
|
||||||
|
// Or pick specific tools
|
||||||
|
const { session } = await createAgentSession({
|
||||||
|
cwd,
|
||||||
|
tools: [createReadTool(cwd), createBashTool(cwd), createGrepTool(cwd)],
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**When you don't need factories:**
|
||||||
|
- If you omit `tools`, pi automatically creates them with the correct `cwd`
|
||||||
|
- If you use `process.cwd()` as your `cwd`, the pre-built instances work fine
|
||||||
|
|
||||||
|
**When you must use factories:**
|
||||||
|
- When you specify both `cwd` (different from `process.cwd()`) AND `tools`
|
||||||
|
|
||||||
> See [examples/sdk/05-tools.ts](../examples/sdk/05-tools.ts)
|
> See [examples/sdk/05-tools.ts](../examples/sdk/05-tools.ts)
|
||||||
|
|
||||||
### Custom Tools
|
### Custom Tools
|
||||||
|
|
@ -788,12 +827,18 @@ buildSystemPrompt
|
||||||
SessionManager
|
SessionManager
|
||||||
SettingsManager
|
SettingsManager
|
||||||
|
|
||||||
// Built-in tools
|
// Built-in tools (use process.cwd())
|
||||||
codingTools
|
codingTools
|
||||||
readOnlyTools
|
readOnlyTools
|
||||||
readTool, bashTool, editTool, writeTool
|
readTool, bashTool, editTool, writeTool
|
||||||
grepTool, findTool, lsTool
|
grepTool, findTool, lsTool
|
||||||
|
|
||||||
|
// Tool factories (for custom cwd)
|
||||||
|
createCodingTools
|
||||||
|
createReadOnlyTools
|
||||||
|
createReadTool, createBashTool, createEditTool, createWriteTool
|
||||||
|
createGrepTool, createFindTool, createLsTool
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
type CreateAgentSessionOptions
|
type CreateAgentSessionOptions
|
||||||
type CreateAgentSessionResult
|
type CreateAgentSessionResult
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,10 @@
|
||||||
* Tools Configuration
|
* Tools Configuration
|
||||||
*
|
*
|
||||||
* Use built-in tool sets, individual tools, or add custom tools.
|
* Use built-in tool sets, individual tools, or add custom tools.
|
||||||
|
*
|
||||||
|
* IMPORTANT: When using a custom `cwd`, you must use the tool factory functions
|
||||||
|
* (createCodingTools, createReadOnlyTools, createReadTool, etc.) to ensure
|
||||||
|
* tools resolve paths relative to your cwd, not process.cwd().
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Type } from "@sinclair/typebox";
|
import { Type } from "@sinclair/typebox";
|
||||||
|
|
@ -9,28 +13,50 @@ import {
|
||||||
createAgentSession,
|
createAgentSession,
|
||||||
discoverCustomTools,
|
discoverCustomTools,
|
||||||
SessionManager,
|
SessionManager,
|
||||||
codingTools, // read, bash, edit, write (default)
|
codingTools, // read, bash, edit, write - uses process.cwd()
|
||||||
readOnlyTools, // read, bash
|
readOnlyTools, // read, grep, find, ls - uses process.cwd()
|
||||||
|
createCodingTools, // Factory: creates tools for specific cwd
|
||||||
|
createReadOnlyTools, // Factory: creates tools for specific cwd
|
||||||
|
createReadTool,
|
||||||
|
createBashTool,
|
||||||
|
createGrepTool,
|
||||||
readTool,
|
readTool,
|
||||||
bashTool,
|
bashTool,
|
||||||
grepTool,
|
grepTool,
|
||||||
type CustomAgentTool,
|
type CustomAgentTool,
|
||||||
} from "../../src/index.js";
|
} from "../../src/index.js";
|
||||||
|
|
||||||
// Read-only mode (no edit/write)
|
// Read-only mode (no edit/write) - uses process.cwd()
|
||||||
const { session: readOnly } = await createAgentSession({
|
const { session: readOnly } = await createAgentSession({
|
||||||
tools: readOnlyTools,
|
tools: readOnlyTools,
|
||||||
sessionManager: SessionManager.inMemory(),
|
sessionManager: SessionManager.inMemory(),
|
||||||
});
|
});
|
||||||
console.log("Read-only session created");
|
console.log("Read-only session created");
|
||||||
|
|
||||||
// Custom tool selection
|
// Custom tool selection - uses process.cwd()
|
||||||
const { session: custom } = await createAgentSession({
|
const { session: custom } = await createAgentSession({
|
||||||
tools: [readTool, bashTool, grepTool],
|
tools: [readTool, bashTool, grepTool],
|
||||||
sessionManager: SessionManager.inMemory(),
|
sessionManager: SessionManager.inMemory(),
|
||||||
});
|
});
|
||||||
console.log("Custom tools session created");
|
console.log("Custom tools session created");
|
||||||
|
|
||||||
|
// With custom cwd - MUST use factory functions!
|
||||||
|
const customCwd = "/path/to/project";
|
||||||
|
const { session: customCwdSession } = await createAgentSession({
|
||||||
|
cwd: customCwd,
|
||||||
|
tools: createCodingTools(customCwd), // Tools resolve paths relative to customCwd
|
||||||
|
sessionManager: SessionManager.inMemory(),
|
||||||
|
});
|
||||||
|
console.log("Custom cwd session created");
|
||||||
|
|
||||||
|
// Or pick specific tools for custom cwd
|
||||||
|
const { session: specificTools } = await createAgentSession({
|
||||||
|
cwd: customCwd,
|
||||||
|
tools: [createReadTool(customCwd), createBashTool(customCwd), createGrepTool(customCwd)],
|
||||||
|
sessionManager: SessionManager.inMemory(),
|
||||||
|
});
|
||||||
|
console.log("Specific tools with custom cwd session created");
|
||||||
|
|
||||||
// Inline custom tool (needs TypeBox schema)
|
// Inline custom tool (needs TypeBox schema)
|
||||||
const weatherTool: CustomAgentTool = {
|
const weatherTool: CustomAgentTool = {
|
||||||
name: "get_weather",
|
name: "get_weather",
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,10 @@
|
||||||
*
|
*
|
||||||
* Replace everything - no discovery, explicit configuration.
|
* Replace everything - no discovery, explicit configuration.
|
||||||
* Still uses OAuth from ~/.pi/agent for convenience.
|
* Still uses OAuth from ~/.pi/agent for convenience.
|
||||||
|
*
|
||||||
|
* IMPORTANT: When providing `tools` with a custom `cwd`, use the tool factory
|
||||||
|
* functions (createReadTool, createBashTool, etc.) to ensure tools resolve
|
||||||
|
* paths relative to your cwd.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Type } from "@sinclair/typebox";
|
import { Type } from "@sinclair/typebox";
|
||||||
|
|
@ -13,8 +17,8 @@ import {
|
||||||
findModel,
|
findModel,
|
||||||
SessionManager,
|
SessionManager,
|
||||||
SettingsManager,
|
SettingsManager,
|
||||||
readTool,
|
createReadTool,
|
||||||
bashTool,
|
createBashTool,
|
||||||
type HookFactory,
|
type HookFactory,
|
||||||
type CustomAgentTool,
|
type CustomAgentTool,
|
||||||
} from "../../src/index.js";
|
} from "../../src/index.js";
|
||||||
|
|
@ -60,8 +64,11 @@ const settingsManager = SettingsManager.inMemory({
|
||||||
retry: { enabled: true, maxRetries: 2 },
|
retry: { enabled: true, maxRetries: 2 },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// When using a custom cwd with explicit tools, use the factory functions
|
||||||
|
const cwd = process.cwd();
|
||||||
|
|
||||||
const { session } = await createAgentSession({
|
const { session } = await createAgentSession({
|
||||||
cwd: process.cwd(),
|
cwd,
|
||||||
agentDir: "/tmp/my-agent",
|
agentDir: "/tmp/my-agent",
|
||||||
|
|
||||||
model,
|
model,
|
||||||
|
|
@ -71,7 +78,8 @@ const { session } = await createAgentSession({
|
||||||
systemPrompt: `You are a minimal assistant.
|
systemPrompt: `You are a minimal assistant.
|
||||||
Available: read, bash, status. Be concise.`,
|
Available: read, bash, status. Be concise.`,
|
||||||
|
|
||||||
tools: [readTool, bashTool],
|
// Use factory functions with the same cwd to ensure path resolution works correctly
|
||||||
|
tools: [createReadTool(cwd), createBashTool(cwd)],
|
||||||
customTools: [{ tool: statusTool }],
|
customTools: [{ tool: statusTool }],
|
||||||
hooks: [{ factory: auditHook }],
|
hooks: [{ factory: auditHook }],
|
||||||
skills: [],
|
skills: [],
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ export async function processFileArguments(fileArgs: string[]): Promise<Processe
|
||||||
|
|
||||||
for (const fileArg of fileArgs) {
|
for (const fileArg of fileArgs) {
|
||||||
// Expand and resolve path (handles ~ expansion and macOS screenshot Unicode spaces)
|
// Expand and resolve path (handles ~ expansion and macOS screenshot Unicode spaces)
|
||||||
const absolutePath = resolve(resolveReadPath(fileArg));
|
const absolutePath = resolve(resolveReadPath(fileArg, process.cwd()));
|
||||||
|
|
||||||
// Check if file exists
|
// Check if file exists
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,15 @@ import {
|
||||||
allTools,
|
allTools,
|
||||||
bashTool,
|
bashTool,
|
||||||
codingTools,
|
codingTools,
|
||||||
|
createBashTool,
|
||||||
|
createCodingTools,
|
||||||
|
createEditTool,
|
||||||
|
createFindTool,
|
||||||
|
createGrepTool,
|
||||||
|
createLsTool,
|
||||||
|
createReadOnlyTools,
|
||||||
|
createReadTool,
|
||||||
|
createWriteTool,
|
||||||
editTool,
|
editTool,
|
||||||
findTool,
|
findTool,
|
||||||
grepTool,
|
grepTool,
|
||||||
|
|
@ -138,6 +147,7 @@ export type { FileSlashCommand } from "./slash-commands.js";
|
||||||
export type { Tool } from "./tools/index.js";
|
export type { Tool } from "./tools/index.js";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
// Pre-built tools (use process.cwd())
|
||||||
readTool,
|
readTool,
|
||||||
bashTool,
|
bashTool,
|
||||||
editTool,
|
editTool,
|
||||||
|
|
@ -148,6 +158,16 @@ export {
|
||||||
codingTools,
|
codingTools,
|
||||||
readOnlyTools,
|
readOnlyTools,
|
||||||
allTools as allBuiltInTools,
|
allTools as allBuiltInTools,
|
||||||
|
// Tool factories (for custom cwd)
|
||||||
|
createCodingTools,
|
||||||
|
createReadOnlyTools,
|
||||||
|
createReadTool,
|
||||||
|
createBashTool,
|
||||||
|
createEditTool,
|
||||||
|
createWriteTool,
|
||||||
|
createGrepTool,
|
||||||
|
createFindTool,
|
||||||
|
createLsTool,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helper Functions
|
// Helper Functions
|
||||||
|
|
@ -526,7 +546,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
||||||
|
|
||||||
const contextFiles = options.contextFiles ?? discoverContextFiles(cwd, agentDir);
|
const contextFiles = options.contextFiles ?? discoverContextFiles(cwd, agentDir);
|
||||||
|
|
||||||
const builtInTools = options.tools ?? codingTools;
|
const builtInTools = options.tools ?? createCodingTools(cwd);
|
||||||
|
|
||||||
let customToolsResult: { tools: LoadedCustomTool[]; setUIContext: (ctx: any, hasUI: boolean) => void };
|
let customToolsResult: { tools: LoadedCustomTool[]; setUIContext: (ctx: any, hasUI: boolean) => void };
|
||||||
if (options.customTools !== undefined) {
|
if (options.customTools !== undefined) {
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,8 @@ export interface BashToolDetails {
|
||||||
fullOutputPath?: string;
|
fullOutputPath?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const bashTool: AgentTool<typeof bashSchema> = {
|
export function createBashTool(cwd: string): AgentTool<typeof bashSchema> {
|
||||||
|
return {
|
||||||
name: "bash",
|
name: "bash",
|
||||||
label: "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.`,
|
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.`,
|
||||||
|
|
@ -40,6 +41,7 @@ export const bashTool: AgentTool<typeof bashSchema> = {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const { shell, args } = getShellConfig();
|
const { shell, args } = getShellConfig();
|
||||||
const child = spawn(shell, [...args, command], {
|
const child = spawn(shell, [...args, command], {
|
||||||
|
cwd,
|
||||||
detached: true,
|
detached: true,
|
||||||
stdio: ["ignore", "pipe", "pipe"],
|
stdio: ["ignore", "pipe", "pipe"],
|
||||||
});
|
});
|
||||||
|
|
@ -203,4 +205,8 @@ export const bashTool: AgentTool<typeof bashSchema> = {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Default bash tool using process.cwd() - for backwards compatibility */
|
||||||
|
export const bashTool = createBashTool(process.cwd());
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,7 @@ import { Type } from "@sinclair/typebox";
|
||||||
import * as Diff from "diff";
|
import * as Diff from "diff";
|
||||||
import { constants } from "fs";
|
import { constants } from "fs";
|
||||||
import { access, readFile, writeFile } from "fs/promises";
|
import { access, readFile, writeFile } from "fs/promises";
|
||||||
import { resolve as resolvePath } from "path";
|
import { resolveToCwd } from "./path-utils.js";
|
||||||
import { expandPath } from "./path-utils.js";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a unified diff string with line numbers and context
|
* Generate a unified diff string with line numbers and context
|
||||||
|
|
@ -107,7 +106,8 @@ const editSchema = Type.Object({
|
||||||
newText: Type.String({ description: "New text to replace the old text with" }),
|
newText: Type.String({ description: "New text to replace the old text with" }),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const editTool: AgentTool<typeof editSchema> = {
|
export function createEditTool(cwd: string): AgentTool<typeof editSchema> {
|
||||||
|
return {
|
||||||
name: "edit",
|
name: "edit",
|
||||||
label: "edit",
|
label: "edit",
|
||||||
description:
|
description:
|
||||||
|
|
@ -118,7 +118,7 @@ export const editTool: AgentTool<typeof editSchema> = {
|
||||||
{ path, oldText, newText }: { path: string; oldText: string; newText: string },
|
{ path, oldText, newText }: { path: string; oldText: string; newText: string },
|
||||||
signal?: AbortSignal,
|
signal?: AbortSignal,
|
||||||
) => {
|
) => {
|
||||||
const absolutePath = resolvePath(expandPath(path));
|
const absolutePath = resolveToCwd(path, cwd);
|
||||||
|
|
||||||
return new Promise<{
|
return new Promise<{
|
||||||
content: Array<{ type: "text"; text: string }>;
|
content: Array<{ type: "text"; text: string }>;
|
||||||
|
|
@ -254,4 +254,8 @@ export const editTool: AgentTool<typeof editSchema> = {
|
||||||
})();
|
})();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Default edit tool using process.cwd() - for backwards compatibility */
|
||||||
|
export const editTool = createEditTool(process.cwd());
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { existsSync } from "fs";
|
||||||
import { globSync } from "glob";
|
import { globSync } from "glob";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { ensureTool } from "../../utils/tools-manager.js";
|
import { ensureTool } from "../../utils/tools-manager.js";
|
||||||
import { expandPath } from "./path-utils.js";
|
import { resolveToCwd } from "./path-utils.js";
|
||||||
import { DEFAULT_MAX_BYTES, formatSize, type TruncationResult, truncateHead } from "./truncate.js";
|
import { DEFAULT_MAX_BYTES, formatSize, type TruncationResult, truncateHead } from "./truncate.js";
|
||||||
|
|
||||||
const findSchema = Type.Object({
|
const findSchema = Type.Object({
|
||||||
|
|
@ -23,7 +23,8 @@ export interface FindToolDetails {
|
||||||
resultLimitReached?: number;
|
resultLimitReached?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const findTool: AgentTool<typeof findSchema> = {
|
export function createFindTool(cwd: string): AgentTool<typeof findSchema> {
|
||||||
|
return {
|
||||||
name: "find",
|
name: "find",
|
||||||
label: "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).`,
|
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).`,
|
||||||
|
|
@ -51,7 +52,7 @@ export const findTool: AgentTool<typeof findSchema> = {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const searchPath = path.resolve(expandPath(searchDir || "."));
|
const searchPath = resolveToCwd(searchDir || ".", cwd);
|
||||||
const effectiveLimit = limit ?? DEFAULT_LIMIT;
|
const effectiveLimit = limit ?? DEFAULT_LIMIT;
|
||||||
|
|
||||||
// Build fd arguments
|
// Build fd arguments
|
||||||
|
|
@ -187,4 +188,8 @@ export const findTool: AgentTool<typeof findSchema> = {
|
||||||
})();
|
})();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Default find tool using process.cwd() - for backwards compatibility */
|
||||||
|
export const findTool = createFindTool(process.cwd());
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { spawn } from "child_process";
|
||||||
import { readFileSync, type Stats, statSync } from "fs";
|
import { readFileSync, type Stats, statSync } from "fs";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { ensureTool } from "../../utils/tools-manager.js";
|
import { ensureTool } from "../../utils/tools-manager.js";
|
||||||
import { expandPath } from "./path-utils.js";
|
import { resolveToCwd } from "./path-utils.js";
|
||||||
import {
|
import {
|
||||||
DEFAULT_MAX_BYTES,
|
DEFAULT_MAX_BYTES,
|
||||||
formatSize,
|
formatSize,
|
||||||
|
|
@ -37,7 +37,8 @@ export interface GrepToolDetails {
|
||||||
linesTruncated?: boolean;
|
linesTruncated?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const grepTool: AgentTool<typeof grepSchema> = {
|
export function createGrepTool(cwd: string): AgentTool<typeof grepSchema> {
|
||||||
|
return {
|
||||||
name: "grep",
|
name: "grep",
|
||||||
label: "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.`,
|
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.`,
|
||||||
|
|
@ -85,7 +86,7 @@ export const grepTool: AgentTool<typeof grepSchema> = {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const searchPath = path.resolve(expandPath(searchDir || "."));
|
const searchPath = resolveToCwd(searchDir || ".", cwd);
|
||||||
let searchStat: Stats;
|
let searchStat: Stats;
|
||||||
try {
|
try {
|
||||||
searchStat = statSync(searchPath);
|
searchStat = statSync(searchPath);
|
||||||
|
|
@ -304,4 +305,8 @@ export const grepTool: AgentTool<typeof grepSchema> = {
|
||||||
})();
|
})();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Default grep tool using process.cwd() - for backwards compatibility */
|
||||||
|
export const grepTool = createGrepTool(process.cwd());
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,32 @@
|
||||||
import type { AgentTool } from "@mariozechner/pi-ai";
|
import type { AgentTool } from "@mariozechner/pi-ai";
|
||||||
|
|
||||||
export { type BashToolDetails, bashTool } from "./bash.js";
|
export { type BashToolDetails, bashTool, createBashTool } from "./bash.js";
|
||||||
export { editTool } from "./edit.js";
|
export { createEditTool, editTool } from "./edit.js";
|
||||||
export { type FindToolDetails, findTool } from "./find.js";
|
export { createFindTool, type FindToolDetails, findTool } from "./find.js";
|
||||||
export { type GrepToolDetails, grepTool } from "./grep.js";
|
export { createGrepTool, type GrepToolDetails, grepTool } from "./grep.js";
|
||||||
export { type LsToolDetails, lsTool } from "./ls.js";
|
export { createLsTool, type LsToolDetails, lsTool } from "./ls.js";
|
||||||
export { type ReadToolDetails, readTool } from "./read.js";
|
export { createReadTool, type ReadToolDetails, readTool } from "./read.js";
|
||||||
export type { TruncationResult } from "./truncate.js";
|
export type { TruncationResult } from "./truncate.js";
|
||||||
export { writeTool } from "./write.js";
|
export { createWriteTool, writeTool } from "./write.js";
|
||||||
|
|
||||||
import { bashTool } from "./bash.js";
|
import { bashTool, createBashTool } from "./bash.js";
|
||||||
import { editTool } from "./edit.js";
|
import { createEditTool, editTool } from "./edit.js";
|
||||||
import { findTool } from "./find.js";
|
import { createFindTool, findTool } from "./find.js";
|
||||||
import { grepTool } from "./grep.js";
|
import { createGrepTool, grepTool } from "./grep.js";
|
||||||
import { lsTool } from "./ls.js";
|
import { createLsTool, lsTool } from "./ls.js";
|
||||||
import { readTool } from "./read.js";
|
import { createReadTool, readTool } from "./read.js";
|
||||||
import { writeTool } from "./write.js";
|
import { createWriteTool, writeTool } from "./write.js";
|
||||||
|
|
||||||
/** Tool type (AgentTool from pi-ai) */
|
/** Tool type (AgentTool from pi-ai) */
|
||||||
export type Tool = AgentTool<any>;
|
export type Tool = AgentTool<any>;
|
||||||
|
|
||||||
// Default tools for full access mode
|
// Default tools for full access mode (using process.cwd())
|
||||||
export const codingTools: Tool[] = [readTool, bashTool, editTool, writeTool];
|
export const codingTools: Tool[] = [readTool, bashTool, editTool, writeTool];
|
||||||
|
|
||||||
// Read-only tools for exploration without modification
|
// Read-only tools for exploration without modification (using process.cwd())
|
||||||
export const readOnlyTools: Tool[] = [readTool, grepTool, findTool, lsTool];
|
export const readOnlyTools: Tool[] = [readTool, grepTool, findTool, lsTool];
|
||||||
|
|
||||||
// All available tools (including read-only exploration tools)
|
// All available tools (using process.cwd())
|
||||||
export const allTools = {
|
export const allTools = {
|
||||||
read: readTool,
|
read: readTool,
|
||||||
bash: bashTool,
|
bash: bashTool,
|
||||||
|
|
@ -38,3 +38,32 @@ export const allTools = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ToolName = keyof typeof allTools;
|
export type ToolName = keyof typeof allTools;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create coding tools configured for a specific working directory.
|
||||||
|
*/
|
||||||
|
export function createCodingTools(cwd: string): Tool[] {
|
||||||
|
return [createReadTool(cwd), createBashTool(cwd), createEditTool(cwd), createWriteTool(cwd)];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create read-only tools configured for a specific working directory.
|
||||||
|
*/
|
||||||
|
export function createReadOnlyTools(cwd: string): Tool[] {
|
||||||
|
return [createReadTool(cwd), createGrepTool(cwd), createFindTool(cwd), createLsTool(cwd)];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create all tools configured for a specific working directory.
|
||||||
|
*/
|
||||||
|
export function createAllTools(cwd: string): Record<ToolName, Tool> {
|
||||||
|
return {
|
||||||
|
read: createReadTool(cwd),
|
||||||
|
bash: createBashTool(cwd),
|
||||||
|
edit: createEditTool(cwd),
|
||||||
|
write: createWriteTool(cwd),
|
||||||
|
grep: createGrepTool(cwd),
|
||||||
|
find: createFindTool(cwd),
|
||||||
|
ls: createLsTool(cwd),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import type { AgentTool } from "@mariozechner/pi-ai";
|
||||||
import { Type } from "@sinclair/typebox";
|
import { Type } from "@sinclair/typebox";
|
||||||
import { existsSync, readdirSync, statSync } from "fs";
|
import { existsSync, readdirSync, statSync } from "fs";
|
||||||
import nodePath from "path";
|
import nodePath from "path";
|
||||||
import { expandPath } from "./path-utils.js";
|
import { resolveToCwd } from "./path-utils.js";
|
||||||
import { DEFAULT_MAX_BYTES, formatSize, type TruncationResult, truncateHead } from "./truncate.js";
|
import { DEFAULT_MAX_BYTES, formatSize, type TruncationResult, truncateHead } from "./truncate.js";
|
||||||
|
|
||||||
const lsSchema = Type.Object({
|
const lsSchema = Type.Object({
|
||||||
|
|
@ -17,12 +17,17 @@ export interface LsToolDetails {
|
||||||
entryLimitReached?: number;
|
entryLimitReached?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const lsTool: AgentTool<typeof lsSchema> = {
|
export function createLsTool(cwd: string): AgentTool<typeof lsSchema> {
|
||||||
|
return {
|
||||||
name: "ls",
|
name: "ls",
|
||||||
label: "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).`,
|
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).`,
|
||||||
parameters: lsSchema,
|
parameters: lsSchema,
|
||||||
execute: async (_toolCallId: string, { path, limit }: { path?: string; limit?: number }, signal?: AbortSignal) => {
|
execute: async (
|
||||||
|
_toolCallId: string,
|
||||||
|
{ path, limit }: { path?: string; limit?: number },
|
||||||
|
signal?: AbortSignal,
|
||||||
|
) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (signal?.aborted) {
|
if (signal?.aborted) {
|
||||||
reject(new Error("Operation aborted"));
|
reject(new Error("Operation aborted"));
|
||||||
|
|
@ -33,7 +38,7 @@ export const lsTool: AgentTool<typeof lsSchema> = {
|
||||||
signal?.addEventListener("abort", onAbort, { once: true });
|
signal?.addEventListener("abort", onAbort, { once: true });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const dirPath = nodePath.resolve(expandPath(path || "."));
|
const dirPath = resolveToCwd(path || ".", cwd);
|
||||||
const effectiveLimit = limit ?? DEFAULT_LIMIT;
|
const effectiveLimit = limit ?? DEFAULT_LIMIT;
|
||||||
|
|
||||||
// Check if path exists
|
// Check if path exists
|
||||||
|
|
@ -128,4 +133,8 @@ export const lsTool: AgentTool<typeof lsSchema> = {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Default ls tool using process.cwd() - for backwards compatibility */
|
||||||
|
export const lsTool = createLsTool(process.cwd());
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { accessSync, constants } from "node:fs";
|
import { accessSync, constants } from "node:fs";
|
||||||
import * as os from "node:os";
|
import * as os from "node:os";
|
||||||
|
import { isAbsolute, resolve as resolvePath } from "node:path";
|
||||||
|
|
||||||
const UNICODE_SPACES = /[\u00A0\u2000-\u200A\u202F\u205F\u3000]/g;
|
const UNICODE_SPACES = /[\u00A0\u2000-\u200A\u202F\u205F\u3000]/g;
|
||||||
const NARROW_NO_BREAK_SPACE = "\u202F";
|
const NARROW_NO_BREAK_SPACE = "\u202F";
|
||||||
|
|
@ -32,17 +33,29 @@ export function expandPath(filePath: string): string {
|
||||||
return normalized;
|
return normalized;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolveReadPath(filePath: string): string {
|
/**
|
||||||
|
* Resolve a path relative to the given cwd.
|
||||||
|
* Handles ~ expansion and absolute paths.
|
||||||
|
*/
|
||||||
|
export function resolveToCwd(filePath: string, cwd: string): string {
|
||||||
const expanded = expandPath(filePath);
|
const expanded = expandPath(filePath);
|
||||||
|
if (isAbsolute(expanded)) {
|
||||||
if (fileExists(expanded)) {
|
|
||||||
return expanded;
|
return expanded;
|
||||||
}
|
}
|
||||||
|
return resolvePath(cwd, expanded);
|
||||||
|
}
|
||||||
|
|
||||||
const macOSVariant = tryMacOSScreenshotPath(expanded);
|
export function resolveReadPath(filePath: string, cwd: string): string {
|
||||||
if (macOSVariant !== expanded && fileExists(macOSVariant)) {
|
const resolved = resolveToCwd(filePath, cwd);
|
||||||
|
|
||||||
|
if (fileExists(resolved)) {
|
||||||
|
return resolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
const macOSVariant = tryMacOSScreenshotPath(resolved);
|
||||||
|
if (macOSVariant !== resolved && fileExists(macOSVariant)) {
|
||||||
return macOSVariant;
|
return macOSVariant;
|
||||||
}
|
}
|
||||||
|
|
||||||
return expanded;
|
return resolved;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ import type { AgentTool, ImageContent, TextContent } from "@mariozechner/pi-ai";
|
||||||
import { Type } from "@sinclair/typebox";
|
import { Type } from "@sinclair/typebox";
|
||||||
import { constants } from "fs";
|
import { constants } from "fs";
|
||||||
import { access, readFile } from "fs/promises";
|
import { access, readFile } from "fs/promises";
|
||||||
import { resolve as resolvePath } from "path";
|
|
||||||
import { detectSupportedImageMimeTypeFromFile } from "../../utils/mime.js";
|
import { detectSupportedImageMimeTypeFromFile } from "../../utils/mime.js";
|
||||||
import { resolveReadPath } from "./path-utils.js";
|
import { resolveReadPath } from "./path-utils.js";
|
||||||
import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize, type TruncationResult, truncateHead } from "./truncate.js";
|
import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize, type TruncationResult, truncateHead } from "./truncate.js";
|
||||||
|
|
@ -17,7 +16,8 @@ export interface ReadToolDetails {
|
||||||
truncation?: TruncationResult;
|
truncation?: TruncationResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const readTool: AgentTool<typeof readSchema> = {
|
export function createReadTool(cwd: string): AgentTool<typeof readSchema> {
|
||||||
|
return {
|
||||||
name: "read",
|
name: "read",
|
||||||
label: "read",
|
label: "read",
|
||||||
description: `Read the contents of a file. Supports text files and images (jpg, png, gif, webp). 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.`,
|
description: `Read the contents of a file. Supports text files and images (jpg, png, gif, webp). 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.`,
|
||||||
|
|
@ -27,7 +27,7 @@ export const readTool: AgentTool<typeof readSchema> = {
|
||||||
{ path, offset, limit }: { path: string; offset?: number; limit?: number },
|
{ path, offset, limit }: { path: string; offset?: number; limit?: number },
|
||||||
signal?: AbortSignal,
|
signal?: AbortSignal,
|
||||||
) => {
|
) => {
|
||||||
const absolutePath = resolvePath(resolveReadPath(path));
|
const absolutePath = resolveReadPath(path, cwd);
|
||||||
|
|
||||||
return new Promise<{ content: (TextContent | ImageContent)[]; details: ReadToolDetails | undefined }>(
|
return new Promise<{ content: (TextContent | ImageContent)[]; details: ReadToolDetails | undefined }>(
|
||||||
(resolve, reject) => {
|
(resolve, reject) => {
|
||||||
|
|
@ -164,4 +164,8 @@ export const readTool: AgentTool<typeof readSchema> = {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Default read tool using process.cwd() - for backwards compatibility */
|
||||||
|
export const readTool = createReadTool(process.cwd());
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,31 @@
|
||||||
import type { AgentTool } from "@mariozechner/pi-ai";
|
import type { AgentTool } from "@mariozechner/pi-ai";
|
||||||
import { Type } from "@sinclair/typebox";
|
import { Type } from "@sinclair/typebox";
|
||||||
import { mkdir, writeFile } from "fs/promises";
|
import { mkdir, writeFile } from "fs/promises";
|
||||||
import { dirname, resolve as resolvePath } from "path";
|
import { dirname } from "path";
|
||||||
import { expandPath } from "./path-utils.js";
|
import { resolveToCwd } from "./path-utils.js";
|
||||||
|
|
||||||
const writeSchema = Type.Object({
|
const writeSchema = Type.Object({
|
||||||
path: Type.String({ description: "Path to the file to write (relative or absolute)" }),
|
path: Type.String({ description: "Path to the file to write (relative or absolute)" }),
|
||||||
content: Type.String({ description: "Content to write to the file" }),
|
content: Type.String({ description: "Content to write to the file" }),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const writeTool: AgentTool<typeof writeSchema> = {
|
export function createWriteTool(cwd: string): AgentTool<typeof writeSchema> {
|
||||||
|
return {
|
||||||
name: "write",
|
name: "write",
|
||||||
label: "write",
|
label: "write",
|
||||||
description:
|
description:
|
||||||
"Write content to a file. Creates the file if it doesn't exist, overwrites if it does. Automatically creates parent directories.",
|
"Write content to a file. Creates the file if it doesn't exist, overwrites if it does. Automatically creates parent directories.",
|
||||||
parameters: writeSchema,
|
parameters: writeSchema,
|
||||||
execute: async (_toolCallId: string, { path, content }: { path: string; content: string }, signal?: AbortSignal) => {
|
execute: async (
|
||||||
const absolutePath = resolvePath(expandPath(path));
|
_toolCallId: string,
|
||||||
|
{ path, content }: { path: string; content: string },
|
||||||
|
signal?: AbortSignal,
|
||||||
|
) => {
|
||||||
|
const absolutePath = resolveToCwd(path, cwd);
|
||||||
const dir = dirname(absolutePath);
|
const dir = dirname(absolutePath);
|
||||||
|
|
||||||
return new Promise<{ content: Array<{ type: "text"; text: string }>; details: undefined }>((resolve, reject) => {
|
return new Promise<{ content: Array<{ type: "text"; text: string }>; details: undefined }>(
|
||||||
|
(resolve, reject) => {
|
||||||
// Check if already aborted
|
// Check if already aborted
|
||||||
if (signal?.aborted) {
|
if (signal?.aborted) {
|
||||||
reject(new Error("Operation aborted"));
|
reject(new Error("Operation aborted"));
|
||||||
|
|
@ -77,6 +83,11 @@ export const writeTool: AgentTool<typeof writeSchema> = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
});
|
|
||||||
},
|
},
|
||||||
};
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Default write tool using process.cwd() - for backwards compatibility */
|
||||||
|
export const writeTool = createWriteTool(process.cwd());
|
||||||
|
|
|
||||||
|
|
@ -93,6 +93,16 @@ export {
|
||||||
configureOAuthStorage,
|
configureOAuthStorage,
|
||||||
// Factory
|
// Factory
|
||||||
createAgentSession,
|
createAgentSession,
|
||||||
|
createBashTool,
|
||||||
|
// Tool factories (for custom cwd)
|
||||||
|
createCodingTools,
|
||||||
|
createEditTool,
|
||||||
|
createFindTool,
|
||||||
|
createGrepTool,
|
||||||
|
createLsTool,
|
||||||
|
createReadOnlyTools,
|
||||||
|
createReadTool,
|
||||||
|
createWriteTool,
|
||||||
// Helpers
|
// Helpers
|
||||||
defaultGetApiKey,
|
defaultGetApiKey,
|
||||||
discoverAvailableModels,
|
discoverAvailableModels,
|
||||||
|
|
@ -106,7 +116,7 @@ export {
|
||||||
type FileSlashCommand,
|
type FileSlashCommand,
|
||||||
findModel as findModelByProviderAndId,
|
findModel as findModelByProviderAndId,
|
||||||
loadSettings,
|
loadSettings,
|
||||||
// Tools
|
// Pre-built tools (use process.cwd())
|
||||||
readOnlyTools,
|
readOnlyTools,
|
||||||
} from "./core/sdk.js";
|
} from "./core/sdk.js";
|
||||||
export {
|
export {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue