@@ -34,6 +34,7 @@ import { InstanceState } from "@/effect/instance-state"
3434import { makeRuntime } from "@/effect/run-service"
3535import { Env } from "../env"
3636import { Question } from "../question"
37+ import { Todo } from "../session/todo"
3738
3839export namespace ToolRegistry {
3940 const log = Log . create ( { service : "tool.registry" } )
@@ -56,172 +57,175 @@ export namespace ToolRegistry {
5657
5758 export class Service extends ServiceMap . Service < Service , Interface > ( ) ( "@opencode/ToolRegistry" ) { }
5859
59- export const layer : Layer . Layer < Service , never , Config . Service | Plugin . Service | Question . Service > = Layer . effect (
60- Service ,
61- Effect . gen ( function * ( ) {
62- const config = yield * Config . Service
63- const plugin = yield * Plugin . Service
64-
65- const build = < T extends Tool . Info > ( tool : T | Effect . Effect < T , never , any > ) =>
66- Effect . isEffect ( tool ) ? tool : Effect . succeed ( tool )
67-
68- const state = yield * InstanceState . make < State > (
69- Effect . fn ( "ToolRegistry.state" ) ( function * ( ctx ) {
70- const custom : Tool . Info [ ] = [ ]
71-
72- function fromPlugin ( id : string , def : ToolDefinition ) : Tool . Info {
73- return {
74- id,
75- init : async ( initCtx ) => ( {
76- parameters : z . object ( def . args ) ,
77- description : def . description ,
78- execute : async ( args , toolCtx ) => {
79- const pluginCtx = {
80- ...toolCtx ,
81- directory : ctx . directory ,
82- worktree : ctx . worktree ,
83- } as unknown as PluginToolContext
84- const result = await def . execute ( args as any , pluginCtx )
85- const out = await Truncate . output ( result , { } , initCtx ?. agent )
86- return {
87- title : "" ,
88- output : out . truncated ? out . content : result ,
89- metadata : { truncated : out . truncated , outputPath : out . truncated ? out . outputPath : undefined } ,
90- }
91- } ,
92- } ) ,
60+ export const layer : Layer . Layer < Service , never , Config . Service | Plugin . Service | Question . Service | Todo . Service > =
61+ Layer . effect (
62+ Service ,
63+ Effect . gen ( function * ( ) {
64+ const config = yield * Config . Service
65+ const plugin = yield * Plugin . Service
66+
67+ const build = < T extends Tool . Info > ( tool : T | Effect . Effect < T , never , any > ) =>
68+ Effect . isEffect ( tool ) ? tool : Effect . succeed ( tool )
69+
70+ const state = yield * InstanceState . make < State > (
71+ Effect . fn ( "ToolRegistry.state" ) ( function * ( ctx ) {
72+ const custom : Tool . Info [ ] = [ ]
73+
74+ function fromPlugin ( id : string , def : ToolDefinition ) : Tool . Info {
75+ return {
76+ id,
77+ init : async ( initCtx ) => ( {
78+ parameters : z . object ( def . args ) ,
79+ description : def . description ,
80+ execute : async ( args , toolCtx ) => {
81+ const pluginCtx = {
82+ ...toolCtx ,
83+ directory : ctx . directory ,
84+ worktree : ctx . worktree ,
85+ } as unknown as PluginToolContext
86+ const result = await def . execute ( args as any , pluginCtx )
87+ const out = await Truncate . output ( result , { } , initCtx ?. agent )
88+ return {
89+ title : "" ,
90+ output : out . truncated ? out . content : result ,
91+ metadata : { truncated : out . truncated , outputPath : out . truncated ? out . outputPath : undefined } ,
92+ }
93+ } ,
94+ } ) ,
95+ }
9396 }
94- }
9597
96- const dirs = yield * config . directories ( )
97- const matches = dirs . flatMap ( ( dir ) =>
98- Glob . scanSync ( "{tool,tools}/*.{js,ts}" , { cwd : dir , absolute : true , dot : true , symlink : true } ) ,
99- )
100- if ( matches . length ) yield * config . waitForDependencies ( )
101- for ( const match of matches ) {
102- const namespace = path . basename ( match , path . extname ( match ) )
103- const mod = yield * Effect . promise (
104- ( ) => import ( process . platform === "win32" ? match : pathToFileURL ( match ) . href ) ,
98+ const dirs = yield * config . directories ( )
99+ const matches = dirs . flatMap ( ( dir ) =>
100+ Glob . scanSync ( "{tool,tools}/*.{js,ts}" , { cwd : dir , absolute : true , dot : true , symlink : true } ) ,
105101 )
106- for ( const [ id , def ] of Object . entries < ToolDefinition > ( mod ) ) {
107- custom . push ( fromPlugin ( id === "default" ? namespace : `${ namespace } _${ id } ` , def ) )
102+ if ( matches . length ) yield * config . waitForDependencies ( )
103+ for ( const match of matches ) {
104+ const namespace = path . basename ( match , path . extname ( match ) )
105+ const mod = yield * Effect . promise (
106+ ( ) => import ( process . platform === "win32" ? match : pathToFileURL ( match ) . href ) ,
107+ )
108+ for ( const [ id , def ] of Object . entries < ToolDefinition > ( mod ) ) {
109+ custom . push ( fromPlugin ( id === "default" ? namespace : `${ namespace } _${ id } ` , def ) )
110+ }
108111 }
109- }
110112
111- const plugins = yield * plugin . list ( )
112- for ( const p of plugins ) {
113- for ( const [ id , def ] of Object . entries ( p . tool ?? { } ) ) {
114- custom . push ( fromPlugin ( id , def ) )
115- }
116- }
117-
118- return { custom }
119- } ) ,
120- )
121-
122- const invalid = yield * build ( InvalidTool )
123- const ask = yield * build ( QuestionTool )
124- const bash = yield * build ( BashTool )
125- const read = yield * build ( ReadTool )
126- const glob = yield * build ( GlobTool )
127- const grep = yield * build ( GrepTool )
128- const edit = yield * build ( EditTool )
129- const write = yield * build ( WriteTool )
130- const task = yield * build ( TaskTool )
131- const fetch = yield * build ( WebFetchTool )
132- const todo = yield * build ( TodoWriteTool )
133- const search = yield * build ( WebSearchTool )
134- const code = yield * build ( CodeSearchTool )
135- const skill = yield * build ( SkillTool )
136- const patch = yield * build ( ApplyPatchTool )
137- const lsp = yield * build ( LspTool )
138- const batch = yield * build ( BatchTool )
139- const plan = yield * build ( PlanExitTool )
140-
141- const all = Effect . fn ( "ToolRegistry.all" ) ( function * ( custom : Tool . Info [ ] ) {
142- const cfg = yield * config . get ( )
143- const question = [ "app" , "cli" , "desktop" ] . includes ( Flag . OPENCODE_CLIENT ) || Flag . OPENCODE_ENABLE_QUESTION_TOOL
144-
145- return [
146- invalid ,
147- ...( question ? [ ask ] : [ ] ) ,
148- bash ,
149- read ,
150- glob ,
151- grep ,
152- edit ,
153- write ,
154- task ,
155- fetch ,
156- todo ,
157- search ,
158- code ,
159- skill ,
160- patch ,
161- ...( Flag . OPENCODE_EXPERIMENTAL_LSP_TOOL ? [ lsp ] : [ ] ) ,
162- ...( cfg . experimental ?. batch_tool === true ? [ batch ] : [ ] ) ,
163- ...( Flag . OPENCODE_EXPERIMENTAL_PLAN_MODE && Flag . OPENCODE_CLIENT === "cli" ? [ plan ] : [ ] ) ,
164- ...custom ,
165- ]
166- } )
167-
168- const ids = Effect . fn ( "ToolRegistry.ids" ) ( function * ( ) {
169- const s = yield * InstanceState . get ( state )
170- const tools = yield * all ( s . custom )
171- return tools . map ( ( t ) => t . id )
172- } )
173-
174- const tools = Effect . fn ( "ToolRegistry.tools" ) ( function * (
175- model : { providerID : ProviderID ; modelID : ModelID } ,
176- agent ?: Agent . Info ,
177- ) {
178- const s = yield * InstanceState . get ( state )
179- const allTools = yield * all ( s . custom )
180- const filtered = allTools . filter ( ( tool ) => {
181- if ( tool . id === "codesearch" || tool . id === "websearch" ) {
182- return model . providerID === ProviderID . opencode || Flag . OPENCODE_ENABLE_EXA
183- }
184-
185- const usePatch =
186- ! ! Env . get ( "OPENCODE_E2E_LLM_URL" ) ||
187- ( model . modelID . includes ( "gpt-" ) && ! model . modelID . includes ( "oss" ) && ! model . modelID . includes ( "gpt-4" ) )
188- if ( tool . id === "apply_patch" ) return usePatch
189- if ( tool . id === "edit" || tool . id === "write" ) return ! usePatch
190-
191- return true
192- } )
193- return yield * Effect . forEach (
194- filtered ,
195- Effect . fnUntraced ( function * ( tool : Tool . Info ) {
196- using _ = log . time ( tool . id )
197- const next = yield * Effect . promise ( ( ) => tool . init ( { agent } ) )
198- const output = {
199- description : next . description ,
200- parameters : next . parameters ,
201- }
202- yield * plugin . trigger ( "tool.definition" , { toolID : tool . id } , output )
203- return {
204- id : tool . id ,
205- description : output . description ,
206- parameters : output . parameters ,
207- execute : next . execute ,
208- formatValidationError : next . formatValidationError ,
113+ const plugins = yield * plugin . list ( )
114+ for ( const p of plugins ) {
115+ for ( const [ id , def ] of Object . entries ( p . tool ?? { } ) ) {
116+ custom . push ( fromPlugin ( id , def ) )
117+ }
209118 }
119+
120+ return { custom }
210121 } ) ,
211- { concurrency : "unbounded" } ,
212122 )
213- } )
214123
215- return Service . of ( { ids, named : { task, read } , tools } )
216- } ) ,
217- )
124+ const invalid = yield * build ( InvalidTool )
125+ const ask = yield * build ( QuestionTool )
126+ const bash = yield * build ( BashTool )
127+ const read = yield * build ( ReadTool )
128+ const glob = yield * build ( GlobTool )
129+ const grep = yield * build ( GrepTool )
130+ const edit = yield * build ( EditTool )
131+ const write = yield * build ( WriteTool )
132+ const task = yield * build ( TaskTool )
133+ const fetch = yield * build ( WebFetchTool )
134+ const todo = yield * build ( TodoWriteTool )
135+ const search = yield * build ( WebSearchTool )
136+ const code = yield * build ( CodeSearchTool )
137+ const skill = yield * build ( SkillTool )
138+ const patch = yield * build ( ApplyPatchTool )
139+ const lsp = yield * build ( LspTool )
140+ const batch = yield * build ( BatchTool )
141+ const plan = yield * build ( PlanExitTool )
142+
143+ const all = Effect . fn ( "ToolRegistry.all" ) ( function * ( custom : Tool . Info [ ] ) {
144+ const cfg = yield * config . get ( )
145+ const question =
146+ [ "app" , "cli" , "desktop" ] . includes ( Flag . OPENCODE_CLIENT ) || Flag . OPENCODE_ENABLE_QUESTION_TOOL
147+
148+ return [
149+ invalid ,
150+ ...( question ? [ ask ] : [ ] ) ,
151+ bash ,
152+ read ,
153+ glob ,
154+ grep ,
155+ edit ,
156+ write ,
157+ task ,
158+ fetch ,
159+ todo ,
160+ search ,
161+ code ,
162+ skill ,
163+ patch ,
164+ ...( Flag . OPENCODE_EXPERIMENTAL_LSP_TOOL ? [ lsp ] : [ ] ) ,
165+ ...( cfg . experimental ?. batch_tool === true ? [ batch ] : [ ] ) ,
166+ ...( Flag . OPENCODE_EXPERIMENTAL_PLAN_MODE && Flag . OPENCODE_CLIENT === "cli" ? [ plan ] : [ ] ) ,
167+ ...custom ,
168+ ]
169+ } )
170+
171+ const ids = Effect . fn ( "ToolRegistry.ids" ) ( function * ( ) {
172+ const s = yield * InstanceState . get ( state )
173+ const tools = yield * all ( s . custom )
174+ return tools . map ( ( t ) => t . id )
175+ } )
176+
177+ const tools = Effect . fn ( "ToolRegistry.tools" ) ( function * (
178+ model : { providerID : ProviderID ; modelID : ModelID } ,
179+ agent ?: Agent . Info ,
180+ ) {
181+ const s = yield * InstanceState . get ( state )
182+ const allTools = yield * all ( s . custom )
183+ const filtered = allTools . filter ( ( tool ) => {
184+ if ( tool . id === "codesearch" || tool . id === "websearch" ) {
185+ return model . providerID === ProviderID . opencode || Flag . OPENCODE_ENABLE_EXA
186+ }
187+
188+ const usePatch =
189+ ! ! Env . get ( "OPENCODE_E2E_LLM_URL" ) ||
190+ ( model . modelID . includes ( "gpt-" ) && ! model . modelID . includes ( "oss" ) && ! model . modelID . includes ( "gpt-4" ) )
191+ if ( tool . id === "apply_patch" ) return usePatch
192+ if ( tool . id === "edit" || tool . id === "write" ) return ! usePatch
193+
194+ return true
195+ } )
196+ return yield * Effect . forEach (
197+ filtered ,
198+ Effect . fnUntraced ( function * ( tool : Tool . Info ) {
199+ using _ = log . time ( tool . id )
200+ const next = yield * Effect . promise ( ( ) => tool . init ( { agent } ) )
201+ const output = {
202+ description : next . description ,
203+ parameters : next . parameters ,
204+ }
205+ yield * plugin . trigger ( "tool.definition" , { toolID : tool . id } , output )
206+ return {
207+ id : tool . id ,
208+ description : output . description ,
209+ parameters : output . parameters ,
210+ execute : next . execute ,
211+ formatValidationError : next . formatValidationError ,
212+ }
213+ } ) ,
214+ { concurrency : "unbounded" } ,
215+ )
216+ } )
217+
218+ return Service . of ( { ids, named : { task, read } , tools } )
219+ } ) ,
220+ )
218221
219222 export const defaultLayer = Layer . unwrap (
220223 Effect . sync ( ( ) =>
221224 layer . pipe (
222225 Layer . provide ( Config . defaultLayer ) ,
223226 Layer . provide ( Plugin . defaultLayer ) ,
224227 Layer . provide ( Question . defaultLayer ) ,
228+ Layer . provide ( Todo . defaultLayer ) ,
225229 ) ,
226230 ) ,
227231 )
0 commit comments