2026-02-25 00:16:17 +00:00
import { PlanExitTool } from "./plan"
2026-04-10 14:38:46 +00:00
import { Session } from "../session"
2026-01-08 03:29:42 +00:00
import { QuestionTool } from "./question"
2025-07-25 17:29:29 +00:00
import { BashTool } from "./bash"
import { EditTool } from "./edit"
import { GlobTool } from "./glob"
import { GrepTool } from "./grep"
import { ReadTool } from "./read"
2026-04-10 02:20:27 +00:00
import { TaskTool } from "./task"
2026-03-22 01:06:01 +00:00
import { TodoWriteTool } from "./todo"
2025-07-25 17:29:29 +00:00
import { WebFetchTool } from "./webfetch"
import { WriteTool } from "./write"
2025-08-04 15:59:42 +00:00
import { InvalidTool } from "./invalid"
2026-04-10 02:20:27 +00:00
import { SkillTool } from "./skill"
2025-09-08 20:25:04 +00:00
import { Tool } from "./tool"
2025-09-18 07:58:21 +00:00
import { Config } from "../config/config"
2026-01-27 01:57:34 +00:00
import { type ToolContext as PluginToolContext , type ToolDefinition } from "@opencode-ai/plugin"
2025-10-26 19:50:41 +00:00
import z from "zod"
2025-09-18 07:58:21 +00:00
import { Plugin } from "../plugin"
2026-04-10 14:38:46 +00:00
import { Provider } from "../provider/provider"
2026-03-12 14:48:17 +00:00
import { ProviderID , type ModelID } from "../provider/schema"
2025-11-10 21:39:40 +00:00
import { WebSearchTool } from "./websearch"
import { CodeSearchTool } from "./codesearch"
import { Flag } from "@/flag/flag"
2025-12-15 02:11:30 +00:00
import { Log } from "@/util/log"
2025-12-22 06:34:21 +00:00
import { LspTool } from "./lsp"
2026-03-18 01:59:54 +00:00
import { Truncate } from "./truncate"
2026-01-18 06:35:09 +00:00
import { ApplyPatchTool } from "./apply_patch"
2026-02-19 18:40:09 +00:00
import { Glob } from "../util/glob"
2026-04-07 23:48:12 +00:00
import path from "path"
2026-02-25 00:24:47 +00:00
import { pathToFileURL } from "url"
2026-04-11 03:06:28 +00:00
import { Effect , Layer , Context } from "effect"
2026-04-10 14:50:13 +00:00
import { FetchHttpClient , HttpClient } from "effect/unstable/http"
2026-04-10 17:26:31 +00:00
import { ChildProcessSpawner } from "effect/unstable/process/ChildProcessSpawner"
import * as CrossSpawnSpawner from "@/effect/cross-spawn-spawner"
2026-04-10 17:56:42 +00:00
import { Ripgrep } from "../file/ripgrep"
2026-04-10 23:42:14 +00:00
import { Format } from "../format"
2026-03-22 01:06:01 +00:00
import { InstanceState } from "@/effect/instance-state"
2026-03-26 00:19:24 +00:00
import { makeRuntime } from "@/effect/run-service"
2026-04-02 18:17:28 +00:00
import { Env } from "../env"
2026-04-03 14:31:00 +00:00
import { Question } from "../question"
2026-04-03 16:05:40 +00:00
import { Todo } from "../session/todo"
2026-04-04 23:06:00 +00:00
import { LSP } from "../lsp"
import { FileTime } from "../file/time"
import { Instruction } from "../session/instruction"
import { AppFileSystem } from "../filesystem"
2026-04-11 02:36:02 +00:00
import { Bus } from "../bus"
2026-04-07 23:48:12 +00:00
import { Agent } from "../agent/agent"
2026-04-10 02:20:27 +00:00
import { Skill } from "../skill"
import { Permission } from "@/permission"
2025-07-25 17:29:29 +00:00
export namespace ToolRegistry {
2025-12-15 02:11:30 +00:00
const log = Log . create ( { service : "tool.registry" } )
2026-04-09 01:07:55 +00:00
type TaskDef = Tool . InferDef < typeof TaskTool >
type ReadDef = Tool . InferDef < typeof ReadTool >
2026-03-22 01:06:01 +00:00
type State = {
2026-04-07 23:48:12 +00:00
custom : Tool.Def [ ]
builtin : Tool.Def [ ]
2026-04-09 01:07:55 +00:00
task : TaskDef
read : ReadDef
2025-09-08 20:25:04 +00:00
}
2026-03-22 01:06:01 +00:00
export interface Interface {
readonly ids : ( ) = > Effect . Effect < string [ ] >
2026-04-07 23:48:12 +00:00
readonly all : ( ) = > Effect . Effect < Tool.Def [ ] >
2026-04-09 01:07:55 +00:00
readonly named : ( ) = > Effect . Effect < { task : TaskDef ; read : ReadDef } >
2026-04-07 23:48:12 +00:00
readonly tools : ( model : {
providerID : ProviderID
modelID : ModelID
agent : Agent.Info
} ) = > Effect . Effect < Tool.Def [ ] >
2025-09-08 20:25:04 +00:00
}
2026-04-11 03:06:28 +00:00
export class Service extends Context . Service < Service , Interface > ( ) ( "@opencode/ToolRegistry" ) { }
2026-03-22 01:06:01 +00:00
2026-04-04 23:06:00 +00:00
export const layer : Layer.Layer <
Service ,
never ,
| Config . Service
| Plugin . Service
| Question . Service
| Todo . Service
2026-04-08 23:02:19 +00:00
| Agent . Service
2026-04-10 02:20:27 +00:00
| Skill . Service
2026-04-10 14:38:46 +00:00
| Session . Service
| Provider . Service
2026-04-04 23:06:00 +00:00
| LSP . Service
| FileTime . Service
| Instruction . Service
| AppFileSystem . Service
2026-04-11 02:36:02 +00:00
| Bus . Service
2026-04-10 14:50:13 +00:00
| HttpClient . HttpClient
2026-04-10 17:26:31 +00:00
| ChildProcessSpawner
2026-04-10 17:56:42 +00:00
| Ripgrep . Service
2026-04-10 23:42:14 +00:00
| Format . Service
2026-04-11 02:36:02 +00:00
| Truncate . Service
2026-04-04 23:06:00 +00:00
> = Layer . effect (
Service ,
Effect . gen ( function * ( ) {
const config = yield * Config . Service
const plugin = yield * Plugin . Service
2026-04-10 02:20:27 +00:00
const agents = yield * Agent . Service
const skill = yield * Skill . Service
2026-04-11 02:36:02 +00:00
const truncate = yield * Truncate . Service
2026-04-04 23:06:00 +00:00
2026-04-11 02:36:02 +00:00
const invalid = yield * InvalidTool
2026-04-08 23:02:19 +00:00
const task = yield * TaskTool
const read = yield * ReadTool
const question = yield * QuestionTool
const todo = yield * TodoWriteTool
2026-04-10 14:07:19 +00:00
const lsptool = yield * LspTool
2026-04-10 14:38:46 +00:00
const plan = yield * PlanExitTool
2026-04-10 14:50:13 +00:00
const webfetch = yield * WebFetchTool
2026-04-10 15:30:38 +00:00
const websearch = yield * WebSearchTool
2026-04-10 17:26:31 +00:00
const bash = yield * BashTool
2026-04-10 15:49:20 +00:00
const codesearch = yield * CodeSearchTool
2026-04-10 17:56:42 +00:00
const globtool = yield * GlobTool
2026-04-10 20:57:12 +00:00
const writetool = yield * WriteTool
2026-04-10 21:10:28 +00:00
const edit = yield * EditTool
2026-04-10 23:20:00 +00:00
const greptool = yield * GrepTool
2026-04-10 23:42:14 +00:00
const patchtool = yield * ApplyPatchTool
2026-04-10 23:49:53 +00:00
const skilltool = yield * SkillTool
2026-04-04 23:06:00 +00:00
const state = yield * InstanceState . make < State > (
Effect . fn ( "ToolRegistry.state" ) ( function * ( ctx ) {
2026-04-07 23:48:12 +00:00
const custom : Tool.Def [ ] = [ ]
2026-04-04 23:06:00 +00:00
2026-04-07 23:48:12 +00:00
function fromPlugin ( id : string , def : ToolDefinition ) : Tool . Def {
2026-04-04 23:06:00 +00:00
return {
id ,
2026-04-07 23:48:12 +00:00
parameters : z.object ( def . args ) ,
description : def.description ,
2026-04-11 02:36:02 +00:00
execute : ( args , toolCtx ) = >
Effect . gen ( function * ( ) {
const pluginCtx : PluginToolContext = {
. . . toolCtx ,
2026-04-11 03:50:50 +00:00
ask : ( req ) = > toolCtx . ask ( req ) ,
2026-04-11 02:36:02 +00:00
directory : ctx.directory ,
worktree : ctx.worktree ,
}
const result = yield * Effect . promise ( ( ) = > def . execute ( args as any , pluginCtx ) )
const agent = yield * Effect . promise ( ( ) = > Agent . get ( toolCtx . agent ) )
const out = yield * truncate . output ( result , { } , agent )
return {
title : "" ,
output : out.truncated ? out.content : result ,
metadata : {
truncated : out.truncated ,
outputPath : out.truncated ? out.outputPath : undefined ,
} ,
}
} ) ,
2026-03-22 01:06:01 +00:00
}
2026-04-04 23:06:00 +00:00
}
2026-03-22 01:06:01 +00:00
2026-04-04 23:06:00 +00:00
const dirs = yield * config . directories ( )
const matches = dirs . flatMap ( ( dir ) = >
Glob . scanSync ( "{tool,tools}/*.{js,ts}" , { cwd : dir , absolute : true , dot : true , symlink : true } ) ,
)
if ( matches . length ) yield * config . waitForDependencies ( )
for ( const match of matches ) {
const namespace = path . basename ( match , path . extname ( match ) )
const mod = yield * Effect . promise (
( ) = > import ( process . platform === "win32" ? match : pathToFileURL ( match ) . href ) ,
2026-03-22 01:06:01 +00:00
)
2026-04-04 23:06:00 +00:00
for ( const [ id , def ] of Object . entries < ToolDefinition > ( mod ) ) {
custom . push ( fromPlugin ( id === "default" ? namespace : ` ${ namespace } _ ${ id } ` , def ) )
2026-03-22 01:06:01 +00:00
}
2026-04-04 23:06:00 +00:00
}
2026-03-22 01:06:01 +00:00
2026-04-04 23:06:00 +00:00
const plugins = yield * plugin . list ( )
for ( const p of plugins ) {
for ( const [ id , def ] of Object . entries ( p . tool ? ? { } ) ) {
custom . push ( fromPlugin ( id , def ) )
2026-03-30 20:06:51 +00:00
}
2026-04-04 23:06:00 +00:00
}
2026-04-03 16:05:40 +00:00
2026-04-07 23:48:12 +00:00
const cfg = yield * config . get ( )
2026-04-08 23:02:19 +00:00
const questionEnabled =
2026-04-07 23:48:12 +00:00
[ "app" , "cli" , "desktop" ] . includes ( Flag . OPENCODE_CLIENT ) || Flag . OPENCODE_ENABLE_QUESTION_TOOL
2026-04-08 23:02:19 +00:00
const tool = yield * Effect . all ( {
2026-04-11 02:36:02 +00:00
invalid : Tool.init ( invalid ) ,
2026-04-10 17:26:31 +00:00
bash : Tool.init ( bash ) ,
2026-04-08 23:02:19 +00:00
read : Tool.init ( read ) ,
2026-04-10 17:56:42 +00:00
glob : Tool.init ( globtool ) ,
2026-04-10 23:20:00 +00:00
grep : Tool.init ( greptool ) ,
2026-04-10 21:10:28 +00:00
edit : Tool.init ( edit ) ,
2026-04-10 20:57:12 +00:00
write : Tool.init ( writetool ) ,
2026-04-08 23:02:19 +00:00
task : Tool.init ( task ) ,
2026-04-10 14:50:13 +00:00
fetch : Tool.init ( webfetch ) ,
2026-04-08 23:02:19 +00:00
todo : Tool.init ( todo ) ,
2026-04-10 15:30:38 +00:00
search : Tool.init ( websearch ) ,
2026-04-10 15:49:20 +00:00
code : Tool.init ( codesearch ) ,
2026-04-10 23:49:53 +00:00
skill : Tool.init ( skilltool ) ,
2026-04-10 23:42:14 +00:00
patch : Tool.init ( patchtool ) ,
2026-04-08 23:02:19 +00:00
question : Tool.init ( question ) ,
2026-04-10 14:07:19 +00:00
lsp : Tool.init ( lsptool ) ,
2026-04-10 14:38:46 +00:00
plan : Tool.init ( plan ) ,
2026-04-08 23:02:19 +00:00
} )
2026-04-07 23:48:12 +00:00
return {
custom ,
2026-04-08 23:02:19 +00:00
builtin : [
tool . invalid ,
. . . ( questionEnabled ? [ tool . question ] : [ ] ) ,
tool . bash ,
tool . read ,
tool . glob ,
tool . grep ,
tool . edit ,
tool . write ,
tool . task ,
tool . fetch ,
tool . todo ,
tool . search ,
tool . code ,
tool . skill ,
tool . patch ,
. . . ( Flag . OPENCODE_EXPERIMENTAL_LSP_TOOL ? [ tool . lsp ] : [ ] ) ,
. . . ( Flag . OPENCODE_EXPERIMENTAL_PLAN_MODE && Flag . OPENCODE_CLIENT === "cli" ? [ tool . plan ] : [ ] ) ,
] ,
2026-04-09 01:07:55 +00:00
task : tool.task ,
read : tool.read ,
2026-04-07 23:48:12 +00:00
}
2026-04-04 23:06:00 +00:00
} ) ,
)
2026-03-22 01:06:01 +00:00
2026-04-07 23:48:12 +00:00
const all : Interface [ "all" ] = Effect . fn ( "ToolRegistry.all" ) ( function * ( ) {
const s = yield * InstanceState . get ( state )
return [ . . . s . builtin , . . . s . custom ] as Tool . Def [ ]
} )
2026-04-03 16:05:40 +00:00
2026-04-07 23:48:12 +00:00
const ids : Interface [ "ids" ] = Effect . fn ( "ToolRegistry.ids" ) ( function * ( ) {
return ( yield * all ( ) ) . map ( ( tool ) = > tool . id )
2026-04-04 23:06:00 +00:00
} )
2026-04-10 02:20:27 +00:00
const describeSkill = Effect . fn ( "ToolRegistry.describeSkill" ) ( function * ( agent : Agent.Info ) {
const list = yield * skill . available ( agent )
if ( list . length === 0 ) return "No skills are currently available."
return [
"Load a specialized skill that provides domain-specific instructions and workflows." ,
"" ,
"When you recognize that a task matches one of the available skills listed below, use this tool to load the full skill instructions." ,
"" ,
"The skill will inject detailed instructions, workflows, and access to bundled resources (scripts, references, templates) into the conversation context." ,
"" ,
'Tool output includes a `<skill_content name="...">` block with the loaded content.' ,
"" ,
"The following skills provide specialized sets of instructions for particular tasks" ,
"Invoke this tool to load a skill when a task matches one of the available skills listed below:" ,
"" ,
Skill . fmt ( list , { verbose : false } ) ,
] . join ( "\n" )
} )
const describeTask = Effect . fn ( "ToolRegistry.describeTask" ) ( function * ( agent : Agent.Info ) {
const items = ( yield * agents . list ( ) ) . filter ( ( item ) = > item . mode !== "primary" )
const filtered = items . filter (
( item ) = > Permission . evaluate ( "task" , item . name , agent . permission ) . action !== "deny" ,
)
const list = filtered . toSorted ( ( a , b ) = > a . name . localeCompare ( b . name ) )
const description = list
. map (
( item ) = >
` - ${ item . name } : ${ item . description ? ? "This subagent should only be called manually by the user." } ` ,
)
. join ( "\n" )
return [ "Available agent types and the tools they have access to:" , description ] . join ( "\n" )
} )
2026-04-07 23:48:12 +00:00
const tools : Interface [ "tools" ] = Effect . fn ( "ToolRegistry.tools" ) ( function * ( input ) {
const filtered = ( yield * all ( ) ) . filter ( ( tool ) = > {
if ( tool . id === CodeSearchTool . id || tool . id === WebSearchTool . id ) {
return input . providerID === ProviderID . opencode || Flag . OPENCODE_ENABLE_EXA
2026-04-04 23:06:00 +00:00
}
const usePatch =
! ! Env . get ( "OPENCODE_E2E_LLM_URL" ) ||
2026-04-07 23:48:12 +00:00
( input . modelID . includes ( "gpt-" ) && ! input . modelID . includes ( "oss" ) && ! input . modelID . includes ( "gpt-4" ) )
if ( tool . id === ApplyPatchTool . id ) return usePatch
if ( tool . id === EditTool . id || tool . id === WriteTool . id ) return ! usePatch
2026-04-04 23:06:00 +00:00
return true
2026-04-03 16:05:40 +00:00
} )
2026-04-07 23:48:12 +00:00
2026-04-04 23:06:00 +00:00
return yield * Effect . forEach (
filtered ,
2026-04-07 23:48:12 +00:00
Effect . fnUntraced ( function * ( tool : Tool.Def ) {
2026-04-04 23:06:00 +00:00
using _ = log . time ( tool . id )
const output = {
2026-04-07 23:48:12 +00:00
description : tool.description ,
parameters : tool.parameters ,
2026-04-04 23:06:00 +00:00
}
yield * plugin . trigger ( "tool.definition" , { toolID : tool.id } , output )
return {
id : tool.id ,
2026-04-07 23:48:12 +00:00
description : [
output . description ,
2026-04-10 02:20:27 +00:00
tool . id === TaskTool . id ? yield * describeTask ( input . agent ) : undefined ,
tool . id === SkillTool . id ? yield * describeSkill ( input . agent ) : undefined ,
2026-04-07 23:48:12 +00:00
]
. filter ( Boolean )
. join ( "\n" ) ,
2026-04-04 23:06:00 +00:00
parameters : output.parameters ,
2026-04-07 23:48:12 +00:00
execute : tool.execute ,
formatValidationError : tool.formatValidationError ,
2026-04-04 23:06:00 +00:00
}
} ) ,
{ concurrency : "unbounded" } ,
)
} )
2026-04-03 16:05:40 +00:00
2026-04-09 01:07:55 +00:00
const named : Interface [ "named" ] = Effect . fn ( "ToolRegistry.named" ) ( function * ( ) {
const s = yield * InstanceState . get ( state )
return { task : s.task , read : s.read }
} )
return Service . of ( { ids , all , named , tools } )
2026-04-04 23:06:00 +00:00
} ) ,
)
2026-03-22 01:06:01 +00:00
2026-04-10 02:28:11 +00:00
export const defaultLayer = Layer . suspend ( ( ) = >
layer . pipe (
Layer . provide ( Config . defaultLayer ) ,
Layer . provide ( Plugin . defaultLayer ) ,
Layer . provide ( Question . defaultLayer ) ,
Layer . provide ( Todo . defaultLayer ) ,
Layer . provide ( Skill . defaultLayer ) ,
Layer . provide ( Agent . defaultLayer ) ,
2026-04-10 14:38:46 +00:00
Layer . provide ( Session . defaultLayer ) ,
Layer . provide ( Provider . defaultLayer ) ,
2026-04-10 02:28:11 +00:00
Layer . provide ( LSP . defaultLayer ) ,
Layer . provide ( FileTime . defaultLayer ) ,
Layer . provide ( Instruction . defaultLayer ) ,
Layer . provide ( AppFileSystem . defaultLayer ) ,
2026-04-11 02:36:02 +00:00
Layer . provide ( Bus . layer ) ,
2026-04-10 14:50:13 +00:00
Layer . provide ( FetchHttpClient . layer ) ,
2026-04-10 23:42:14 +00:00
Layer . provide ( Format . defaultLayer ) ,
2026-04-10 17:26:31 +00:00
Layer . provide ( CrossSpawnSpawner . defaultLayer ) ,
2026-04-10 17:56:42 +00:00
Layer . provide ( Ripgrep . defaultLayer ) ,
2026-04-11 02:36:02 +00:00
Layer . provide ( Truncate . defaultLayer ) ,
2026-04-03 14:31:00 +00:00
) ,
2026-03-27 13:53:00 +00:00
)
const { runPromise } = makeRuntime ( Service , defaultLayer )
2026-03-22 01:06:01 +00:00
2025-09-18 07:58:21 +00:00
export async function ids() {
2026-03-22 01:06:01 +00:00
return runPromise ( ( svc ) = > svc . ids ( ) )
2025-07-25 17:29:29 +00:00
}
2026-04-07 23:48:12 +00:00
export async function tools ( input : {
providerID : ProviderID
modelID : ModelID
agent : Agent.Info
} ) : Promise < ( Tool . Def & { id : string } ) [ ] > {
return runPromise ( ( svc ) = > svc . tools ( input ) )
2025-07-25 17:29:29 +00:00
}
}