import type { ConnectorTool } from "@rakazo/adapter-kit"; import { agentConnectionTools, builtinAgentTools } from "./builtin-tools.js"; import { selectCloudAgentTools } from "./cloud-agent-tools-select.js"; import { selectMemoryTools } from "./memory-tools.js"; import { filterImageReturningComputerTools } from "./model-vision.js"; import { filterBuiltinToolsForRun, filterBuiltinToolsForThread, withScheduleTargetBot, } from "./schedule-tools.js"; /** Browser tools that require a graphical computer (Chrome). */ export const PAGE_BROWSER_TOOL_NAMES = new Set([ "browser_navigate", "browser_snapshot", "browser_act", ]); /** Builtin tools only the user's main bot may use; every other bot's runs never see them. */ export const MAIN_ONLY_BUILTIN_TOOLS: ReadonlySet = new Set([ "delegate_task", "notify_user", "update_user_profile", ]); const BUILTIN_TOOL_NAMES = [...builtinAgentTools, ...agentConnectionTools].map((tool) => tool.name); /** The bot fields that decide which builtin tools its runs may use. */ export interface BuiltinToolPolicy { kind: string; restrictBuiltinTools: boolean; allowedBuiltinTools: readonly string[]; } /** * Builtin tool names a bot's allowlist can choose from, in catalog order. Agent connection tools * only ever reach chat-linked bots, so only those list them. */ export function builtinToolNamesForBot(bot: { kind: string; chatLinked: boolean }): string[] { return [...builtinAgentTools, ...(bot.chatLinked ? agentConnectionTools : [])] .map((tool) => tool.name) .filter((name) => bot.kind === "main" || !MAIN_ONLY_BUILTIN_TOOLS.has(name)); } /** Names that are not builtin tools; an allowlist must not carry typos or connector tools. */ export function unknownBuiltinToolNames(names: readonly string[]): string[] { return names.filter((name) => !BUILTIN_TOOL_NAMES.includes(name)); } /** * Least privilege per bot: main-only tools stay with the main bot, a restricted bot keeps only * its allowlist (empty means none), and the main bot may schedule for another bot. */ export function filterBuiltinToolsForBot( tools: ConnectorTool[], bot: BuiltinToolPolicy, ): ConnectorTool[] { const main = bot.kind === "main"; const allowed = new Set(bot.allowedBuiltinTools); return tools .filter( (tool) => (main || !MAIN_ONLY_BUILTIN_TOOLS.has(tool.name)) && (!bot.restrictBuiltinTools || allowed.has(tool.name)), ) .map((tool) => (main && tool.name === "schedule_create" ? withScheduleTargetBot(tool) : tool)); } export function filterPageBrowserTools( tools: T[], pageBrowserAllowed: boolean, ): T[] { if (pageBrowserAllowed) return tools; return tools.filter((tool) => !PAGE_BROWSER_TOOL_NAMES.has(tool.name)); } export function selectBuiltinToolsForRun(options: { graphicalToolsAllowed: boolean; /** Page browser tools need a graphical computer (Chrome), not model vision. */ pageBrowserAllowed?: boolean; groupId: string | null; trigger: string; semanticMemoryEnabled: boolean; cloudAgentEnabled?: boolean; messagingChannelRun: boolean; /** Cross-owner agent connections only exist for chat-linked bots. */ agentConnectionsEnabled?: boolean; /** Answering another owner's agent: what the run hears is not the user, so no profile writes. */ foreignAgentRun?: boolean; bot: BuiltinToolPolicy; }) { const selected = selectCloudAgentTools( selectMemoryTools( filterBuiltinToolsForRun( filterBuiltinToolsForThread( filterPageBrowserTools( filterImageReturningComputerTools(builtinAgentTools, options.graphicalToolsAllowed), options.pageBrowserAllowed ?? options.graphicalToolsAllowed, ), options.groupId, ), options.trigger, ), options.semanticMemoryEnabled, ), Boolean(options.cloudAgentEnabled), ).filter( (tool) => !( options.foreignAgentRun && (tool.name === "update_user_profile" || tool.name === "notify_user") ) && (!options.messagingChannelRun || (![ "remember", "save_memory", "recall_memory", "forget_memory", "update_user_profile", "notify_user", ].includes(tool.name) && !tool.name.startsWith("scratchpad_"))), ); return filterBuiltinToolsForBot( options.agentConnectionsEnabled ? [...selected, ...agentConnectionTools] : selected, options.bot, ); }