mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-17 11:04:51 +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]
|
||||
|
||||
### 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
|
||||
|
||||
### Added
|
||||
|
|
|
|||
|
|
@ -307,7 +307,7 @@ const { session } = await createAgentSession({
|
|||
```typescript
|
||||
import {
|
||||
codingTools, // read, bash, edit, write (default)
|
||||
readOnlyTools, // read, bash
|
||||
readOnlyTools, // read, grep, find, ls
|
||||
readTool, bashTool, editTool, writeTool,
|
||||
grepTool, findTool, lsTool,
|
||||
} 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)
|
||||
|
||||
### Custom Tools
|
||||
|
|
@ -788,12 +827,18 @@ buildSystemPrompt
|
|||
SessionManager
|
||||
SettingsManager
|
||||
|
||||
// Built-in tools
|
||||
// Built-in tools (use process.cwd())
|
||||
codingTools
|
||||
readOnlyTools
|
||||
readTool, bashTool, editTool, writeTool
|
||||
grepTool, findTool, lsTool
|
||||
|
||||
// Tool factories (for custom cwd)
|
||||
createCodingTools
|
||||
createReadOnlyTools
|
||||
createReadTool, createBashTool, createEditTool, createWriteTool
|
||||
createGrepTool, createFindTool, createLsTool
|
||||
|
||||
// Types
|
||||
type CreateAgentSessionOptions
|
||||
type CreateAgentSessionResult
|
||||
|
|
|
|||
|
|
@ -2,6 +2,10 @@
|
|||
* Tools Configuration
|
||||
*
|
||||
* 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";
|
||||
|
|
@ -9,28 +13,50 @@ import {
|
|||
createAgentSession,
|
||||
discoverCustomTools,
|
||||
SessionManager,
|
||||
codingTools, // read, bash, edit, write (default)
|
||||
readOnlyTools, // read, bash
|
||||
codingTools, // read, bash, edit, write - uses process.cwd()
|
||||
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,
|
||||
bashTool,
|
||||
grepTool,
|
||||
type CustomAgentTool,
|
||||
} from "../../src/index.js";
|
||||
|
||||
// Read-only mode (no edit/write)
|
||||
// Read-only mode (no edit/write) - uses process.cwd()
|
||||
const { session: readOnly } = await createAgentSession({
|
||||
tools: readOnlyTools,
|
||||
sessionManager: SessionManager.inMemory(),
|
||||
});
|
||||
console.log("Read-only session created");
|
||||
|
||||
// Custom tool selection
|
||||
// Custom tool selection - uses process.cwd()
|
||||
const { session: custom } = await createAgentSession({
|
||||
tools: [readTool, bashTool, grepTool],
|
||||
sessionManager: SessionManager.inMemory(),
|
||||
});
|
||||
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)
|
||||
const weatherTool: CustomAgentTool = {
|
||||
name: "get_weather",
|
||||
|
|
|
|||
|
|
@ -3,6 +3,10 @@
|
|||
*
|
||||
* Replace everything - no discovery, explicit configuration.
|
||||
* 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";
|
||||
|
|
@ -13,8 +17,8 @@ import {
|
|||
findModel,
|
||||
SessionManager,
|
||||
SettingsManager,
|
||||
readTool,
|
||||
bashTool,
|
||||
createReadTool,
|
||||
createBashTool,
|
||||
type HookFactory,
|
||||
type CustomAgentTool,
|
||||
} from "../../src/index.js";
|
||||
|
|
@ -60,8 +64,11 @@ const settingsManager = SettingsManager.inMemory({
|
|||
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({
|
||||
cwd: process.cwd(),
|
||||
cwd,
|
||||
agentDir: "/tmp/my-agent",
|
||||
|
||||
model,
|
||||
|
|
@ -71,7 +78,8 @@ const { session } = await createAgentSession({
|
|||
systemPrompt: `You are a minimal assistant.
|
||||
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 }],
|
||||
hooks: [{ factory: auditHook }],
|
||||
skills: [],
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ export async function processFileArguments(fileArgs: string[]): Promise<Processe
|
|||
|
||||
for (const fileArg of fileArgs) {
|
||||
// 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
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -58,6 +58,15 @@ import {
|
|||
allTools,
|
||||
bashTool,
|
||||
codingTools,
|
||||
createBashTool,
|
||||
createCodingTools,
|
||||
createEditTool,
|
||||
createFindTool,
|
||||
createGrepTool,
|
||||
createLsTool,
|
||||
createReadOnlyTools,
|
||||
createReadTool,
|
||||
createWriteTool,
|
||||
editTool,
|
||||
findTool,
|
||||
grepTool,
|
||||
|
|
@ -138,6 +147,7 @@ export type { FileSlashCommand } from "./slash-commands.js";
|
|||
export type { Tool } from "./tools/index.js";
|
||||
|
||||
export {
|
||||
// Pre-built tools (use process.cwd())
|
||||
readTool,
|
||||
bashTool,
|
||||
editTool,
|
||||
|
|
@ -148,6 +158,16 @@ export {
|
|||
codingTools,
|
||||
readOnlyTools,
|
||||
allTools as allBuiltInTools,
|
||||
// Tool factories (for custom cwd)
|
||||
createCodingTools,
|
||||
createReadOnlyTools,
|
||||
createReadTool,
|
||||
createBashTool,
|
||||
createEditTool,
|
||||
createWriteTool,
|
||||
createGrepTool,
|
||||
createFindTool,
|
||||
createLsTool,
|
||||
};
|
||||
|
||||
// Helper Functions
|
||||
|
|
@ -526,7 +546,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|||
|
||||
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 };
|
||||
if (options.customTools !== undefined) {
|
||||
|
|
|
|||
|
|
@ -26,7 +26,8 @@ export interface BashToolDetails {
|
|||
fullOutputPath?: string;
|
||||
}
|
||||
|
||||
export const bashTool: AgentTool<typeof bashSchema> = {
|
||||
export function createBashTool(cwd: string): AgentTool<typeof bashSchema> {
|
||||
return {
|
||||
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.`,
|
||||
|
|
@ -40,6 +41,7 @@ export const bashTool: AgentTool<typeof bashSchema> = {
|
|||
return new Promise((resolve, reject) => {
|
||||
const { shell, args } = getShellConfig();
|
||||
const child = spawn(shell, [...args, command], {
|
||||
cwd,
|
||||
detached: true,
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
});
|
||||
|
|
@ -204,3 +206,7 @@ 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 { constants } from "fs";
|
||||
import { access, readFile, writeFile } from "fs/promises";
|
||||
import { resolve as resolvePath } from "path";
|
||||
import { expandPath } from "./path-utils.js";
|
||||
import { resolveToCwd } from "./path-utils.js";
|
||||
|
||||
/**
|
||||
* 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" }),
|
||||
});
|
||||
|
||||
export const editTool: AgentTool<typeof editSchema> = {
|
||||
export function createEditTool(cwd: string): AgentTool<typeof editSchema> {
|
||||
return {
|
||||
name: "edit",
|
||||
label: "edit",
|
||||
description:
|
||||
|
|
@ -118,7 +118,7 @@ export const editTool: AgentTool<typeof editSchema> = {
|
|||
{ path, oldText, newText }: { path: string; oldText: string; newText: string },
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
const absolutePath = resolvePath(expandPath(path));
|
||||
const absolutePath = resolveToCwd(path, cwd);
|
||||
|
||||
return new Promise<{
|
||||
content: Array<{ type: "text"; text: string }>;
|
||||
|
|
@ -255,3 +255,7 @@ 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 path from "path";
|
||||
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";
|
||||
|
||||
const findSchema = Type.Object({
|
||||
|
|
@ -23,7 +23,8 @@ export interface FindToolDetails {
|
|||
resultLimitReached?: number;
|
||||
}
|
||||
|
||||
export const findTool: AgentTool<typeof findSchema> = {
|
||||
export function createFindTool(cwd: string): AgentTool<typeof findSchema> {
|
||||
return {
|
||||
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).`,
|
||||
|
|
@ -51,7 +52,7 @@ export const findTool: AgentTool<typeof findSchema> = {
|
|||
return;
|
||||
}
|
||||
|
||||
const searchPath = path.resolve(expandPath(searchDir || "."));
|
||||
const searchPath = resolveToCwd(searchDir || ".", cwd);
|
||||
const effectiveLimit = limit ?? DEFAULT_LIMIT;
|
||||
|
||||
// Build fd arguments
|
||||
|
|
@ -188,3 +189,7 @@ 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 path from "path";
|
||||
import { ensureTool } from "../../utils/tools-manager.js";
|
||||
import { expandPath } from "./path-utils.js";
|
||||
import { resolveToCwd } from "./path-utils.js";
|
||||
import {
|
||||
DEFAULT_MAX_BYTES,
|
||||
formatSize,
|
||||
|
|
@ -37,7 +37,8 @@ export interface GrepToolDetails {
|
|||
linesTruncated?: boolean;
|
||||
}
|
||||
|
||||
export const grepTool: AgentTool<typeof grepSchema> = {
|
||||
export function createGrepTool(cwd: string): AgentTool<typeof grepSchema> {
|
||||
return {
|
||||
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.`,
|
||||
|
|
@ -85,7 +86,7 @@ export const grepTool: AgentTool<typeof grepSchema> = {
|
|||
return;
|
||||
}
|
||||
|
||||
const searchPath = path.resolve(expandPath(searchDir || "."));
|
||||
const searchPath = resolveToCwd(searchDir || ".", cwd);
|
||||
let searchStat: Stats;
|
||||
try {
|
||||
searchStat = statSync(searchPath);
|
||||
|
|
@ -305,3 +306,7 @@ 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";
|
||||
|
||||
export { type BashToolDetails, bashTool } from "./bash.js";
|
||||
export { editTool } from "./edit.js";
|
||||
export { type FindToolDetails, findTool } from "./find.js";
|
||||
export { type GrepToolDetails, grepTool } from "./grep.js";
|
||||
export { type LsToolDetails, lsTool } from "./ls.js";
|
||||
export { type ReadToolDetails, readTool } from "./read.js";
|
||||
export { type BashToolDetails, bashTool, createBashTool } from "./bash.js";
|
||||
export { createEditTool, editTool } from "./edit.js";
|
||||
export { createFindTool, type FindToolDetails, findTool } from "./find.js";
|
||||
export { createGrepTool, type GrepToolDetails, grepTool } from "./grep.js";
|
||||
export { createLsTool, type LsToolDetails, lsTool } from "./ls.js";
|
||||
export { createReadTool, type ReadToolDetails, readTool } from "./read.js";
|
||||
export type { TruncationResult } from "./truncate.js";
|
||||
export { writeTool } from "./write.js";
|
||||
export { createWriteTool, writeTool } from "./write.js";
|
||||
|
||||
import { bashTool } from "./bash.js";
|
||||
import { editTool } from "./edit.js";
|
||||
import { findTool } from "./find.js";
|
||||
import { grepTool } from "./grep.js";
|
||||
import { lsTool } from "./ls.js";
|
||||
import { readTool } from "./read.js";
|
||||
import { writeTool } from "./write.js";
|
||||
import { bashTool, createBashTool } from "./bash.js";
|
||||
import { createEditTool, editTool } from "./edit.js";
|
||||
import { createFindTool, findTool } from "./find.js";
|
||||
import { createGrepTool, grepTool } from "./grep.js";
|
||||
import { createLsTool, lsTool } from "./ls.js";
|
||||
import { createReadTool, readTool } from "./read.js";
|
||||
import { createWriteTool, writeTool } from "./write.js";
|
||||
|
||||
/** Tool type (AgentTool from pi-ai) */
|
||||
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];
|
||||
|
||||
// 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];
|
||||
|
||||
// All available tools (including read-only exploration tools)
|
||||
// All available tools (using process.cwd())
|
||||
export const allTools = {
|
||||
read: readTool,
|
||||
bash: bashTool,
|
||||
|
|
@ -38,3 +38,32 @@ export const 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 { existsSync, readdirSync, statSync } from "fs";
|
||||
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";
|
||||
|
||||
const lsSchema = Type.Object({
|
||||
|
|
@ -17,12 +17,17 @@ export interface LsToolDetails {
|
|||
entryLimitReached?: number;
|
||||
}
|
||||
|
||||
export const lsTool: AgentTool<typeof lsSchema> = {
|
||||
export function createLsTool(cwd: string): AgentTool<typeof lsSchema> {
|
||||
return {
|
||||
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).`,
|
||||
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) => {
|
||||
if (signal?.aborted) {
|
||||
reject(new Error("Operation aborted"));
|
||||
|
|
@ -33,7 +38,7 @@ export const lsTool: AgentTool<typeof lsSchema> = {
|
|||
signal?.addEventListener("abort", onAbort, { once: true });
|
||||
|
||||
try {
|
||||
const dirPath = nodePath.resolve(expandPath(path || "."));
|
||||
const dirPath = resolveToCwd(path || ".", cwd);
|
||||
const effectiveLimit = limit ?? DEFAULT_LIMIT;
|
||||
|
||||
// Check if path exists
|
||||
|
|
@ -129,3 +134,7 @@ 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 * as os from "node:os";
|
||||
import { isAbsolute, resolve as resolvePath } from "node:path";
|
||||
|
||||
const UNICODE_SPACES = /[\u00A0\u2000-\u200A\u202F\u205F\u3000]/g;
|
||||
const NARROW_NO_BREAK_SPACE = "\u202F";
|
||||
|
|
@ -32,17 +33,29 @@ export function expandPath(filePath: string): string {
|
|||
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);
|
||||
|
||||
if (fileExists(expanded)) {
|
||||
if (isAbsolute(expanded)) {
|
||||
return expanded;
|
||||
}
|
||||
return resolvePath(cwd, expanded);
|
||||
}
|
||||
|
||||
const macOSVariant = tryMacOSScreenshotPath(expanded);
|
||||
if (macOSVariant !== expanded && fileExists(macOSVariant)) {
|
||||
export function resolveReadPath(filePath: string, cwd: string): string {
|
||||
const resolved = resolveToCwd(filePath, cwd);
|
||||
|
||||
if (fileExists(resolved)) {
|
||||
return resolved;
|
||||
}
|
||||
|
||||
const macOSVariant = tryMacOSScreenshotPath(resolved);
|
||||
if (macOSVariant !== resolved && fileExists(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 { constants } from "fs";
|
||||
import { access, readFile } from "fs/promises";
|
||||
import { resolve as resolvePath } from "path";
|
||||
import { detectSupportedImageMimeTypeFromFile } from "../../utils/mime.js";
|
||||
import { resolveReadPath } from "./path-utils.js";
|
||||
import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize, type TruncationResult, truncateHead } from "./truncate.js";
|
||||
|
|
@ -17,7 +16,8 @@ export interface ReadToolDetails {
|
|||
truncation?: TruncationResult;
|
||||
}
|
||||
|
||||
export const readTool: AgentTool<typeof readSchema> = {
|
||||
export function createReadTool(cwd: string): AgentTool<typeof readSchema> {
|
||||
return {
|
||||
name: "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.`,
|
||||
|
|
@ -27,7 +27,7 @@ export const readTool: AgentTool<typeof readSchema> = {
|
|||
{ path, offset, limit }: { path: string; offset?: number; limit?: number },
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
const absolutePath = resolvePath(resolveReadPath(path));
|
||||
const absolutePath = resolveReadPath(path, cwd);
|
||||
|
||||
return new Promise<{ content: (TextContent | ImageContent)[]; details: ReadToolDetails | undefined }>(
|
||||
(resolve, reject) => {
|
||||
|
|
@ -165,3 +165,7 @@ 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 } from "@sinclair/typebox";
|
||||
import { mkdir, writeFile } from "fs/promises";
|
||||
import { dirname, resolve as resolvePath } from "path";
|
||||
import { expandPath } from "./path-utils.js";
|
||||
import { dirname } from "path";
|
||||
import { resolveToCwd } from "./path-utils.js";
|
||||
|
||||
const writeSchema = Type.Object({
|
||||
path: Type.String({ description: "Path to the file to write (relative or absolute)" }),
|
||||
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",
|
||||
label: "write",
|
||||
description:
|
||||
"Write content to a file. Creates the file if it doesn't exist, overwrites if it does. Automatically creates parent directories.",
|
||||
parameters: writeSchema,
|
||||
execute: async (_toolCallId: string, { path, content }: { path: string; content: string }, signal?: AbortSignal) => {
|
||||
const absolutePath = resolvePath(expandPath(path));
|
||||
execute: async (
|
||||
_toolCallId: string,
|
||||
{ path, content }: { path: string; content: string },
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
const absolutePath = resolveToCwd(path, cwd);
|
||||
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
|
||||
if (signal?.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,
|
||||
// Factory
|
||||
createAgentSession,
|
||||
createBashTool,
|
||||
// Tool factories (for custom cwd)
|
||||
createCodingTools,
|
||||
createEditTool,
|
||||
createFindTool,
|
||||
createGrepTool,
|
||||
createLsTool,
|
||||
createReadOnlyTools,
|
||||
createReadTool,
|
||||
createWriteTool,
|
||||
// Helpers
|
||||
defaultGetApiKey,
|
||||
discoverAvailableModels,
|
||||
|
|
@ -106,7 +116,7 @@ export {
|
|||
type FileSlashCommand,
|
||||
findModel as findModelByProviderAndId,
|
||||
loadSettings,
|
||||
// Tools
|
||||
// Pre-built tools (use process.cwd())
|
||||
readOnlyTools,
|
||||
} from "./core/sdk.js";
|
||||
export {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue