import type { ToolDefinition } from "@opencode-ai/plugin" import type { AvailableCategory, } from "../agents/dynamic-agent-prompt-builder" import type { OhMyOpenCodeConfig } from "../config" import type { PluginContext, ToolsRecord } from "./types" import { builtinTools, createBackgroundTools, createCallOmoAgent, createLookAt, createSkillMcpTool, createSkillTool, createGrepTools, createGlobTools, createAstGrepTools, createSessionManagerTools, createDelegateTask, discoverCommandsSync, interactive_bash, createTaskCreateTool, createTaskGetTool, createTaskList, createTaskUpdateTool, createHashlineEditTool, } from "../tools" import { getMainSessionID } from "../features/claude-code-session-state" import { filterDisabledTools } from "../shared/disabled-tools" import { log } from "../shared" import type { Managers } from "../create-managers" import type { SkillContext } from "./skill-context" import { normalizeToolArgSchemas } from "./normalize-tool-arg-schemas" export type ToolRegistryResult = { filteredTools: ToolsRecord taskSystemEnabled: boolean } export function createToolRegistry(args: { ctx: PluginContext pluginConfig: OhMyOpenCodeConfig managers: Pick skillContext: SkillContext availableCategories: AvailableCategory[] }): ToolRegistryResult { const { ctx, pluginConfig, managers, skillContext, availableCategories } = args const backgroundTools = createBackgroundTools(managers.backgroundManager, ctx.client) const callOmoAgent = createCallOmoAgent( ctx, managers.backgroundManager, pluginConfig.disabled_agents ?? [], pluginConfig.agents, pluginConfig.categories, ) const isMultimodalLookerEnabled = !(pluginConfig.disabled_agents ?? []).some( (agent) => agent.toLowerCase() === "multimodal-looker", ) const lookAt = isMultimodalLookerEnabled ? createLookAt(ctx) : null const delegateTask = createDelegateTask({ manager: managers.backgroundManager, client: ctx.client, directory: ctx.directory, userCategories: pluginConfig.categories, agentOverrides: pluginConfig.agents, gitMasterConfig: pluginConfig.git_master, sisyphusJuniorModel: pluginConfig.agents?.["sisyphus-junior"]?.model, browserProvider: skillContext.browserProvider, disabledSkills: skillContext.disabledSkills, availableCategories, availableSkills: skillContext.availableSkills, syncPollTimeoutMs: pluginConfig.background_task?.syncPollTimeoutMs, onSyncSessionCreated: async (event) => { log("[index] onSyncSessionCreated callback", { sessionID: event.sessionID, parentID: event.parentID, title: event.title, }) await managers.tmuxSessionManager.onSessionCreated({ type: "session.created", properties: { info: { id: event.sessionID, parentID: event.parentID, title: event.title, }, }, }) }, }) const getSessionIDForMcp = (): string => getMainSessionID() || "" const skillMcpTool = createSkillMcpTool({ manager: managers.skillMcpManager, getLoadedSkills: () => skillContext.mergedSkills, getSessionID: getSessionIDForMcp, }) const commands = discoverCommandsSync(ctx.directory, { pluginsEnabled: pluginConfig.claude_code?.plugins ?? true, enabledPluginsOverride: pluginConfig.claude_code?.plugins_override, }) const skillTool = createSkillTool({ commands, skills: skillContext.mergedSkills, mcpManager: managers.skillMcpManager, getSessionID: getSessionIDForMcp, gitMasterConfig: pluginConfig.git_master, }) // task_system defaults to true since v3.14 — delegation (oracle, subagents) requires it const taskSystemEnabled = pluginConfig.experimental?.task_system ?? true const taskToolsRecord: Record = taskSystemEnabled ? { task_create: createTaskCreateTool(pluginConfig, ctx), task_get: createTaskGetTool(pluginConfig), task_list: createTaskList(pluginConfig), task_update: createTaskUpdateTool(pluginConfig, ctx), } : {} const hashlineEnabled = pluginConfig.hashline_edit ?? false const hashlineToolsRecord: Record = hashlineEnabled ? { edit: createHashlineEditTool(ctx) } : {} const allTools: Record = { ...builtinTools, ...createGrepTools(ctx), ...createGlobTools(ctx), ...createAstGrepTools(ctx), ...createSessionManagerTools(ctx), ...backgroundTools, call_omo_agent: callOmoAgent, ...(lookAt ? { look_at: lookAt } : {}), task: delegateTask, skill_mcp: skillMcpTool, skill: skillTool, interactive_bash, ...taskToolsRecord, ...hashlineToolsRecord, } for (const toolDefinition of Object.values(allTools)) { normalizeToolArgSchemas(toolDefinition) } const filteredTools: ToolsRecord = filterDisabledTools(allTools, pluginConfig.disabled_tools) const maxTools = pluginConfig.experimental?.max_tools if (maxTools) { const estimatedBuiltinTools = 20 const pluginToolBudget = maxTools - estimatedBuiltinTools const toolEntries = Object.entries(filteredTools) if (pluginToolBudget > 0 && toolEntries.length > pluginToolBudget) { const excess = toolEntries.length - pluginToolBudget log(`[tool-registry] Tool count (${toolEntries.length} plugin + ~${estimatedBuiltinTools} builtin = ~${toolEntries.length + estimatedBuiltinTools}) exceeds max_tools=${maxTools}. Trimming ${excess} lower-priority tools.`) const lowPriorityTools = [ "session_list", "session_read", "session_search", "session_info", "call_omo_agent", "interactive_bash", "look_at", "task_create", "task_get", "task_list", "task_update", ] let removed = 0 for (const toolName of lowPriorityTools) { if (removed >= excess) break if (filteredTools[toolName]) { delete filteredTools[toolName] removed += 1 } } if (removed < excess) { log(`[tool-registry] WARNING: Could not trim enough tools. ${toolEntries.length - removed} plugin tools remain.`) } } } return { filteredTools, taskSystemEnabled, } }