multica/src/agent/tools.ts
Jiang Bohan 321b48a1cf feat(agent): register cron tool in agent toolset
Add createCronTool to the agent's default tools list so agents
can use the cron tool to manage scheduled tasks.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-06 14:47:54 +08:00

182 lines
5.5 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 { 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;
code?: string;
retryable?: boolean;
details?: Record<string, unknown>;
};
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,
});
tools.push(sessionsSpawnTool 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);
}