import type { AgentOptions } from "./types.js"; import { createCodingTools } from "@mariozechner/pi-coding-agent"; import type { AgentTool, AgentToolResult } from "@mariozechner/pi-agent-core"; import type { TSchema } from "@sinclair/typebox"; import { createExecTool } from "./tools/exec.js"; import { createProcessTool } from "./tools/process.js"; import { createGlobTool } from "./tools/glob.js"; import { createWebFetchTool, createWebSearchTool } from "./tools/web/index.js"; import { createSessionsSpawnTool } from "./tools/sessions-spawn.js"; import { filterTools } from "./tools/policy.js"; import { isMulticaError, isRetryableError } from "../shared/errors.js"; // Re-export resolveModel from providers for backwards compatibility export { resolveModel } from "./providers/index.js"; /** Options for creating tools */ export interface CreateToolsOptions { cwd: string; /** Whether this agent is a subagent (passed to sessions_spawn tool) */ isSubagent?: boolean | undefined; /** Session ID of the agent (passed to sessions_spawn tool) */ sessionId?: string | undefined; } type ToolErrorPayload = { error: true; message: string; name?: string; code?: string; retryable?: boolean; details?: Record; }; function toToolErrorPayload(error: unknown): ToolErrorPayload { if (isMulticaError(error)) { return { error: true, message: error.message, name: error.name, code: error.code, retryable: error.retryable, details: error.details, }; } if (error instanceof Error) { return { error: true, message: error.message, name: error.name, retryable: isRetryableError(error), }; } return { error: true, message: String(error), }; } function toolErrorResult(error: unknown): AgentToolResult { const payload = toToolErrorPayload(error); return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }], details: payload, }; } function wrapTool( tool: AgentTool, ): AgentTool { const execute = tool.execute; return { ...tool, execute: async (...args) => { try { return await execute(...args); } catch (error) { return toolErrorResult(error) as AgentToolResult; } }, }; } /** * Create all available tools. * This returns the full set before policy filtering. */ export function createAllTools(options: CreateToolsOptions | string): AgentTool[] { // Support legacy string argument for backwards compatibility const opts: CreateToolsOptions = typeof options === "string" ? { cwd: options } : options; const { cwd, isSubagent, sessionId } = opts; const baseTools = createCodingTools(cwd).filter( (tool) => tool.name !== "bash", ) as AgentTool[]; const execTool = createExecTool(cwd); const processTool = createProcessTool(cwd); const globTool = createGlobTool(cwd); const webFetchTool = createWebFetchTool(); const webSearchTool = createWebSearchTool(); const tools: AgentTool[] = [ ...baseTools, execTool as AgentTool, processTool as AgentTool, globTool as AgentTool, webFetchTool as AgentTool, webSearchTool as AgentTool, ]; // Add sessions_spawn tool (will be filtered by policy for subagents) const sessionsSpawnTool = createSessionsSpawnTool({ isSubagent: isSubagent ?? false, sessionId, }); tools.push(sessionsSpawnTool as AgentTool); return tools; } /** * Resolve tools for an agent with policy filtering. * * Applies 4-layer filtering: * 1. Profile (minimal/coding/web/full) * 2. Global allow/deny * 3. Provider-specific rules * 4. Subagent restrictions */ export function resolveTools(options: AgentOptions): AgentTool[] { const cwd = options.cwd ?? process.cwd(); // Create all tools const allTools = createAllTools({ cwd, isSubagent: options.isSubagent, sessionId: options.sessionId, }); // Apply policy filtering const filtered = filterTools(allTools, { config: options.tools, provider: options.provider, isSubagent: options.isSubagent, }); return filtered.map((tool) => wrapTool(tool)); } /** * Get all available tool names (for debugging/listing). */ export function getAllToolNames(cwd?: string): string[] { const tools = createAllTools({ cwd: cwd ?? process.cwd() }); return tools.map((t) => t.name); }