From a550ebf6821225757f0b70b16ff493dc08ea216a Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Sat, 11 Apr 2026 13:47:26 -0400 Subject: [PATCH 1/2] refactor(tool): yield Truncate and Agent in Tool.define, close over in wrap Services are resolved at define-time (in registry context), passed into wrap as closed-over values. Info.init() and Tool.init() stay R=never. Tests updated to provide Truncate.defaultLayer and Agent.defaultLayer. --- .../opencode/test/tool/apply_patch.test.ts | 9 +-------- packages/opencode/test/tool/edit.test.ts | 10 +--------- packages/opencode/test/tool/grep.test.ts | 4 +--- packages/opencode/test/tool/question.test.ts | 4 +--- packages/opencode/test/tool/skill.test.ts | 4 +--- packages/opencode/test/tool/webfetch.test.ts | 18 +++++++++++++----- 6 files changed, 18 insertions(+), 31 deletions(-) diff --git a/packages/opencode/test/tool/apply_patch.test.ts b/packages/opencode/test/tool/apply_patch.test.ts index 03220ea3b1..d65b1126c4 100644 --- a/packages/opencode/test/tool/apply_patch.test.ts +++ b/packages/opencode/test/tool/apply_patch.test.ts @@ -14,14 +14,7 @@ import { tmpdir } from "../fixture/fixture" import { SessionID, MessageID } from "../../src/session/schema" const runtime = ManagedRuntime.make( - Layer.mergeAll( - LSP.defaultLayer, - AppFileSystem.defaultLayer, - Format.defaultLayer, - Bus.layer, - Truncate.defaultLayer, - Agent.defaultLayer, - ), + Layer.mergeAll(LSP.defaultLayer, AppFileSystem.defaultLayer, Format.defaultLayer, Bus.layer, Truncate.defaultLayer, Agent.defaultLayer), ) const baseCtx = { diff --git a/packages/opencode/test/tool/edit.test.ts b/packages/opencode/test/tool/edit.test.ts index e3f28df35d..c19c22d2c5 100644 --- a/packages/opencode/test/tool/edit.test.ts +++ b/packages/opencode/test/tool/edit.test.ts @@ -36,15 +36,7 @@ async function touch(file: string, time: number) { } const runtime = ManagedRuntime.make( - Layer.mergeAll( - LSP.defaultLayer, - FileTime.defaultLayer, - AppFileSystem.defaultLayer, - Format.defaultLayer, - Bus.layer, - Truncate.defaultLayer, - Agent.defaultLayer, - ), + Layer.mergeAll(LSP.defaultLayer, FileTime.defaultLayer, AppFileSystem.defaultLayer, Format.defaultLayer, Bus.layer, Truncate.defaultLayer, Agent.defaultLayer), ) afterAll(async () => { diff --git a/packages/opencode/test/tool/grep.test.ts b/packages/opencode/test/tool/grep.test.ts index 4715ad9251..926b26003c 100644 --- a/packages/opencode/test/tool/grep.test.ts +++ b/packages/opencode/test/tool/grep.test.ts @@ -9,9 +9,7 @@ import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" import { Truncate } from "../../src/tool/truncate" import { Agent } from "../../src/agent/agent" -const runtime = ManagedRuntime.make( - Layer.mergeAll(CrossSpawnSpawner.defaultLayer, Truncate.defaultLayer, Agent.defaultLayer), -) +const runtime = ManagedRuntime.make(Layer.mergeAll(CrossSpawnSpawner.defaultLayer, Truncate.defaultLayer, Agent.defaultLayer)) function initGrep() { return runtime.runPromise(GrepTool.pipe(Effect.flatMap((info) => info.init()))) diff --git a/packages/opencode/test/tool/question.test.ts b/packages/opencode/test/tool/question.test.ts index eb69f1d966..4b94552368 100644 --- a/packages/opencode/test/tool/question.test.ts +++ b/packages/opencode/test/tool/question.test.ts @@ -21,9 +21,7 @@ const ctx = { ask: () => Effect.void, } -const it = testEffect( - Layer.mergeAll(Question.defaultLayer, CrossSpawnSpawner.defaultLayer, Truncate.defaultLayer, Agent.defaultLayer), -) +const it = testEffect(Layer.mergeAll(Question.defaultLayer, CrossSpawnSpawner.defaultLayer, Truncate.defaultLayer, Agent.defaultLayer)) const pending = Effect.fn("QuestionToolTest.pending")(function* (question: Question.Interface) { for (;;) { diff --git a/packages/opencode/test/tool/skill.test.ts b/packages/opencode/test/tool/skill.test.ts index a3873dbebe..85af7f3789 100644 --- a/packages/opencode/test/tool/skill.test.ts +++ b/packages/opencode/test/tool/skill.test.ts @@ -152,9 +152,7 @@ Use this skill. await Instance.provide({ directory: tmp.path, fn: async () => { - const runtime = ManagedRuntime.make( - Layer.mergeAll(Skill.defaultLayer, Ripgrep.defaultLayer, Truncate.defaultLayer, Agent.defaultLayer), - ) + const runtime = ManagedRuntime.make(Layer.mergeAll(Skill.defaultLayer, Ripgrep.defaultLayer, Truncate.defaultLayer, Agent.defaultLayer)) const info = await runtime.runPromise(SkillTool) const tool = await runtime.runPromise(info.init()) const requests: Array> = [] diff --git a/packages/opencode/test/tool/webfetch.test.ts b/packages/opencode/test/tool/webfetch.test.ts index 7d2ff1dcab..789d2ca474 100644 --- a/packages/opencode/test/tool/webfetch.test.ts +++ b/packages/opencode/test/tool/webfetch.test.ts @@ -26,10 +26,9 @@ async function withFetch(fetch: (req: Request) => Response | Promise, await fn(server.url) } -function exec(args: { url: string; format: "text" | "markdown" | "html" }) { +function initTool() { return WebFetchTool.pipe( Effect.flatMap((info) => info.init()), - Effect.flatMap((tool) => tool.execute(args, ctx)), Effect.provide(Layer.mergeAll(FetchHttpClient.layer, Truncate.defaultLayer, Agent.defaultLayer)), Effect.runPromise, ) @@ -44,7 +43,10 @@ describe("tool.webfetch", () => { await Instance.provide({ directory: projectRoot, fn: async () => { - const result = await exec({ url: new URL("/image.png", url).toString(), format: "markdown" }) + const webfetch = await initTool() + const result = await Effect.runPromise( + webfetch.execute({ url: new URL("/image.png", url).toString(), format: "markdown" }, ctx), + ) expect(result.output).toBe("Image fetched successfully") expect(result.attachments).toBeDefined() expect(result.attachments?.length).toBe(1) @@ -72,7 +74,10 @@ describe("tool.webfetch", () => { await Instance.provide({ directory: projectRoot, fn: async () => { - const result = await exec({ url: new URL("/image.svg", url).toString(), format: "html" }) + const webfetch = await initTool() + const result = await Effect.runPromise( + webfetch.execute({ url: new URL("/image.svg", url).toString(), format: "html" }, ctx), + ) expect(result.output).toContain(" { await Instance.provide({ directory: projectRoot, fn: async () => { - const result = await exec({ url: new URL("/file.txt", url).toString(), format: "text" }) + const webfetch = await initTool() + const result = await Effect.runPromise( + webfetch.execute({ url: new URL("/file.txt", url).toString(), format: "text" }, ctx), + ) expect(result.output).toBe("hello from webfetch") expect(result.attachments).toBeUndefined() }, From 7bdb4e6280aaa620e9ceb6808196d4c9e9cdb20f Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Sat, 11 Apr 2026 21:12:33 -0400 Subject: [PATCH 2/2] test(webfetch): keep tool execution in scope Execute the webfetch tool inside the same provided Effect scope used for initialization so the HTTP client layer stays alive for the assertions instead of interrupting the test fibers. --- packages/opencode/test/tool/webfetch.test.ts | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/packages/opencode/test/tool/webfetch.test.ts b/packages/opencode/test/tool/webfetch.test.ts index 789d2ca474..7d2ff1dcab 100644 --- a/packages/opencode/test/tool/webfetch.test.ts +++ b/packages/opencode/test/tool/webfetch.test.ts @@ -26,9 +26,10 @@ async function withFetch(fetch: (req: Request) => Response | Promise, await fn(server.url) } -function initTool() { +function exec(args: { url: string; format: "text" | "markdown" | "html" }) { return WebFetchTool.pipe( Effect.flatMap((info) => info.init()), + Effect.flatMap((tool) => tool.execute(args, ctx)), Effect.provide(Layer.mergeAll(FetchHttpClient.layer, Truncate.defaultLayer, Agent.defaultLayer)), Effect.runPromise, ) @@ -43,10 +44,7 @@ describe("tool.webfetch", () => { await Instance.provide({ directory: projectRoot, fn: async () => { - const webfetch = await initTool() - const result = await Effect.runPromise( - webfetch.execute({ url: new URL("/image.png", url).toString(), format: "markdown" }, ctx), - ) + const result = await exec({ url: new URL("/image.png", url).toString(), format: "markdown" }) expect(result.output).toBe("Image fetched successfully") expect(result.attachments).toBeDefined() expect(result.attachments?.length).toBe(1) @@ -74,10 +72,7 @@ describe("tool.webfetch", () => { await Instance.provide({ directory: projectRoot, fn: async () => { - const webfetch = await initTool() - const result = await Effect.runPromise( - webfetch.execute({ url: new URL("/image.svg", url).toString(), format: "html" }, ctx), - ) + const result = await exec({ url: new URL("/image.svg", url).toString(), format: "html" }) expect(result.output).toContain(" { await Instance.provide({ directory: projectRoot, fn: async () => { - const webfetch = await initTool() - const result = await Effect.runPromise( - webfetch.execute({ url: new URL("/file.txt", url).toString(), format: "text" }, ctx), - ) + const result = await exec({ url: new URL("/file.txt", url).toString(), format: "text" }) expect(result.output).toBe("hello from webfetch") expect(result.attachments).toBeUndefined() },