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