You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: packages/opencode/specs/effect-migration.md
+7-7Lines changed: 7 additions & 7 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -229,24 +229,24 @@ Still open:
229
229
230
230
## Tool interface → Effect
231
231
232
-
Once individual tools are effectified, change `Tool.Info` (`tool/tool.ts`) so `init`and `execute`return `Effect` instead of `Promise`. This lets tool implementations compose natively with the Effect pipeline rather than being wrapped in `Effect.promise()` at the call site. Requires:
232
+
`Tool.Def.execute` and `Tool.Info.init`already return `Effect`on this branch. Tool definitions should now stay Effect-native all the way through initialization instead of using Promise-returning init callbacks. Tools can still use lazy init callbacks when they need instance-bound state at init time, but those callbacks should return `Effect`, not `Promise`. Remaining work is:
233
233
234
-
1. Migrate each tool to return Effects
235
-
2.Update`Tool.define()`factory to work with Effects
236
-
3. Update `SessionPrompt`to `yield*` tool results instead of `await`ing
234
+
1. Migrate each tool body to return Effects
235
+
2.Keep`Tool.define()`inputs Effect-native
236
+
3. Update remaining callers to `yield*` tool initialization instead of `await`ing
237
237
238
238
### Tool migration details
239
239
240
-
Until the tool interface itself returns `Effect`, use this transitional pattern for migrated tools:
240
+
With `Tool.Info.init()` now effectful, use this transitional pattern for migrated tools that still need Promise-based boundaries internally:
241
241
242
242
-`Tool.defineEffect(...)` should `yield*` the services the tool depends on and close over them in the returned tool definition.
243
-
- Keep the bridge at the Promise boundary only. Prefer a single `Effect.runPromise(...)` in the temporary `async execute(...)` implementation, and move the inner logic into `Effect.fn(...)` helpers instead of scattering `runPromise` islands through the tool body.
243
+
- Keep the bridge at the Promise boundary only inside the tool body when required by external APIs. Do not return Promise-based init callbacks from `Tool.define()`.
244
244
- If a tool starts requiring new services, wire them into `ToolRegistry.defaultLayer` so production callers resolve the same dependencies as tests.
245
245
246
246
Tool tests should use the existing Effect helpers in `packages/opencode/test/lib/effect.ts`:
247
247
248
248
- Use `testEffect(...)` / `it.live(...)` instead of creating fake local wrappers around effectful tools.
249
-
- Yield the real tool export, then initialize it: `const info = yield* ReadTool`, `const tool = yield* Effect.promise(() => info.init())`.
249
+
- Yield the real tool export, then initialize it: `const info = yield* ReadTool`, `const tool = yield* info.init()`.
250
250
- Run tests inside a real instance with `provideTmpdirInstance(...)` or `provideInstance(tmpdirScoped(...))` so instance-scoped services resolve exactly as they do in production.
251
251
252
252
This keeps migrated tool tests aligned with the production service graph today, and makes the eventual `Tool.Info` → `Effect` cleanup mostly mechanical later.
? "If the commands depend on each other and must run sequentially, avoid '&&' in this shell because Windows PowerShell 5.1 does not support it. Use PowerShell conditionals such as `cmd1; if ($?) { cmd2 }` when later commands must depend on earlier success."
463
-
: "If the commands depend on each other and must run sequentially, use a single Bash call with '&&' to chain them together (e.g., `git add . && git commit -m \"message\" && git push`). For instance, if one operation must complete before another starts (like mkdir before cp, Write before Bash for git operations, or git add before git commit), run these operations sequentially instead."
thrownewError(`Invalid timeout value: ${params.timeout}. Timeout must be a positive number.`)
481
-
}
482
-
consttimeout=params.timeout??DEFAULT_TIMEOUT
483
-
constps=PS.has(name)
484
-
constroot=yield*parse(params.command,ps)
485
-
constscan=yield*collect(root,cwd,ps,shell)
486
-
if(!Instance.containsPath(cwd))scan.dirs.add(cwd)
487
-
yield*ask(ctx,scan)
488
-
489
-
returnyield*run(
490
-
{
491
-
shell,
492
-
name,
493
-
command: params.command,
494
-
cwd,
495
-
env: yield*shellEnv(ctx,cwd),
496
-
timeout,
497
-
description: params.description,
498
-
},
499
-
ctx,
500
-
)
501
-
}),
502
-
}
503
-
}
457
+
return()=>
458
+
Effect.sync(()=>{
459
+
constshell=Shell.acceptable()
460
+
constname=Shell.name(shell)
461
+
constchain=
462
+
name==="powershell"
463
+
? "If the commands depend on each other and must run sequentially, avoid '&&' in this shell because Windows PowerShell 5.1 does not support it. Use PowerShell conditionals such as `cmd1; if ($?) { cmd2 }` when later commands must depend on earlier success."
464
+
: "If the commands depend on each other and must run sequentially, use a single Bash call with '&&' to chain them together (e.g., `git add . && git commit -m \"message\" && git push`). For instance, if one operation must complete before another starts (like mkdir before cp, Write before Bash for git operations, or git add before git commit), run these operations sequentially instead."
? "Load a specialized skill that provides domain-specific instructions and workflows. No skills are currently available."
27
-
: [
28
-
"Load a specialized skill that provides domain-specific instructions and workflows.",
29
-
"",
30
-
"When you recognize that a task matches one of the available skills listed below, use this tool to load the full skill instructions.",
31
-
"",
32
-
"The skill will inject detailed instructions, workflows, and access to bundled resources (scripts, references, templates) into the conversation context.",
33
-
"",
34
-
'Tool output includes a `<skill_content name="...">` block with the loaded content.',
35
-
"",
36
-
"The following skills provide specialized sets of instructions for particular tasks",
37
-
"Invoke this tool to load a skill when a task matches one of the available skills listed below:",
38
-
"",
39
-
Skill.fmt(list,{verbose: false}),
40
-
].join("\n")
24
+
constdescription=
25
+
list.length===0
26
+
? "Load a specialized skill that provides domain-specific instructions and workflows. No skills are currently available."
27
+
: [
28
+
"Load a specialized skill that provides domain-specific instructions and workflows.",
29
+
"",
30
+
"When you recognize that a task matches one of the available skills listed below, use this tool to load the full skill instructions.",
31
+
"",
32
+
"The skill will inject detailed instructions, workflows, and access to bundled resources (scripts, references, templates) into the conversation context.",
33
+
"",
34
+
'Tool output includes a `<skill_content name="...">` block with the loaded content.',
35
+
"",
36
+
"The following skills provide specialized sets of instructions for particular tasks",
37
+
"Invoke this tool to load a skill when a task matches one of the available skills listed below:",
0 commit comments