multica/src/agent/tools.ts
Jiang Bohan 5380b146b3 chore(deps): upgrade pi-ai and pi-agent-core to 0.52.9
Upgrade @mariozechner/pi-ai and @mariozechner/pi-agent-core from 0.50.3
to 0.52.9 to support latest models (claude-opus-4-6, o3, o3-mini).

Breaking type changes addressed:
- exactOptionalPropertyTypes: use conditional spread or `| undefined`
- TOOL_PROFILES removed: strip all profile references from CLI
- AgentMessage union requires timestamp: cast test fixtures
- AsyncAgent.id → sessionId
- Add explicit callback parameter types for SDK event handlers

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 19:13:38 +08:00

187 lines
5.8 KiB
TypeScript

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 { createSessionsListTool } from "./tools/sessions-list.js";
import { createMemorySearchTool } from "./tools/memory-search.js";
import { createCronTool } from "./tools/cron/index.js";
import { filterTools } from "./tools/policy.js";
import { isMulticaError, isRetryableError } from "../shared/errors.js";
import type { ExecApprovalCallback } from "./tools/exec-approval-types.js";
// Re-export resolveModel from providers for backwards compatibility
export { resolveModel } from "./providers/index.js";
/** Options for creating tools */
export interface CreateToolsOptions {
cwd: string;
/** Profile directory for memory_search tool (optional) */
profileDir?: string | undefined;
/** 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;
/** Callback invoked when exec tool needs approval before running a command */
onExecApprovalNeeded?: ExecApprovalCallback | undefined;
}
type ToolErrorPayload = {
error: true;
message: string;
name?: string | undefined;
code?: string | undefined;
retryable?: boolean | undefined;
details?: Record<string, unknown> | undefined;
};
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<ToolErrorPayload> {
const payload = toToolErrorPayload(error);
return {
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
details: payload,
};
}
function wrapTool<TParams extends TSchema, TResult>(
tool: AgentTool<TParams, TResult>,
): AgentTool<TParams, TResult> {
const execute = tool.execute;
return {
...tool,
execute: async (...args) => {
try {
return await execute(...args);
} catch (error) {
return toolErrorResult(error) as AgentToolResult<TResult>;
}
},
};
}
/**
* Create all available tools.
* This returns the full set before policy filtering.
*/
export function createAllTools(options: CreateToolsOptions | string): AgentTool<any>[] {
// Support legacy string argument for backwards compatibility
const opts: CreateToolsOptions = typeof options === "string" ? { cwd: options } : options;
const { cwd, profileDir, isSubagent, sessionId } = opts;
const baseTools = createCodingTools(cwd).filter(
(tool) => tool.name !== "bash",
) as AgentTool<any>[];
const execTool = createExecTool(cwd, opts.onExecApprovalNeeded);
const processTool = createProcessTool(cwd);
const globTool = createGlobTool(cwd);
const webFetchTool = createWebFetchTool();
const webSearchTool = createWebSearchTool();
const cronTool = createCronTool();
const tools: AgentTool<any>[] = [
...baseTools,
execTool as AgentTool<any>,
processTool as AgentTool<any>,
globTool as AgentTool<any>,
webFetchTool as AgentTool<any>,
webSearchTool as AgentTool<any>,
cronTool as AgentTool<any>,
];
// Add memory_search tool if profileDir is provided
if (profileDir) {
const memorySearchTool = createMemorySearchTool(profileDir);
tools.push(memorySearchTool as AgentTool<any>);
}
// Add sessions_spawn tool (will be filtered by policy for subagents)
const sessionsSpawnTool = createSessionsSpawnTool({
isSubagent: isSubagent ?? false,
...(sessionId !== undefined ? { sessionId } : {}),
});
tools.push(sessionsSpawnTool as AgentTool<any>);
// Add sessions_list tool
const sessionsListTool = createSessionsListTool({ ...(sessionId !== undefined ? { sessionId } : {}) });
tools.push(sessionsListTool as AgentTool<any>);
return tools;
}
/** Extended options for resolveTools that includes profileDir */
export interface ResolveToolsOptions extends AgentOptions {
/** Profile directory for memory_search tool (computed from profileId if not provided) */
profileDir?: string | undefined;
}
/**
* 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: ResolveToolsOptions): AgentTool<any>[] {
const cwd = options.cwd ?? process.cwd();
// Create all tools
const allTools = createAllTools({
cwd,
profileDir: options.profileDir,
isSubagent: options.isSubagent,
sessionId: options.sessionId,
onExecApprovalNeeded: options.onExecApprovalNeeded,
});
// 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);
}