From 87e23abb10625bae4194406545e41beebb1b5a06 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Fri, 10 Apr 2026 23:25:43 -0400 Subject: [PATCH 1/6] refactor: remove ProviderAuth facade (#21983) --- packages/opencode/src/provider/auth.ts | 19 ------------ .../opencode/src/server/routes/provider.ts | 31 ++++++++++++------- .../test/plugin/auth-override.test.ts | 9 ++++-- 3 files changed, 27 insertions(+), 32 deletions(-) diff --git a/packages/opencode/src/provider/auth.ts b/packages/opencode/src/provider/auth.ts index 3823baf135..e410b86365 100644 --- a/packages/opencode/src/provider/auth.ts +++ b/packages/opencode/src/provider/auth.ts @@ -2,7 +2,6 @@ import type { AuthOAuthResult, Hooks } from "@opencode-ai/plugin" import { NamedError } from "@opencode-ai/util/error" import { Auth } from "@/auth" import { InstanceState } from "@/effect/instance-state" -import { makeRuntime } from "@/effect/run-service" import { Plugin } from "../plugin" import { ProviderID } from "./schema" import { Array as Arr, Effect, Layer, Record, Result, Context } from "effect" @@ -232,22 +231,4 @@ export namespace ProviderAuth { export const defaultLayer = Layer.suspend(() => layer.pipe(Layer.provide(Auth.defaultLayer), Layer.provide(Plugin.defaultLayer)), ) - - const { runPromise } = makeRuntime(Service, defaultLayer) - - export async function methods() { - return runPromise((svc) => svc.methods()) - } - - export async function authorize(input: { - providerID: ProviderID - method: number - inputs?: Record - }): Promise { - return runPromise((svc) => svc.authorize(input)) - } - - export async function callback(input: { providerID: ProviderID; method: number; code?: string }) { - return runPromise((svc) => svc.callback(input)) - } } diff --git a/packages/opencode/src/server/routes/provider.ts b/packages/opencode/src/server/routes/provider.ts index 59a4a686b2..efd126ea0f 100644 --- a/packages/opencode/src/server/routes/provider.ts +++ b/packages/opencode/src/server/routes/provider.ts @@ -6,6 +6,7 @@ import { Provider } from "../../provider/provider" import { ModelsDev } from "../../provider/models" import { ProviderAuth } from "../../provider/auth" import { ProviderID } from "../../provider/schema" +import { AppRuntime } from "../../effect/app-runtime" import { mapValues } from "remeda" import { errors } from "../error" import { lazy } from "../../util/lazy" @@ -81,7 +82,7 @@ export const ProviderRoutes = lazy(() => }, }), async (c) => { - return c.json(await ProviderAuth.methods()) + return c.json(await AppRuntime.runPromise(ProviderAuth.Service.use((svc) => svc.methods()))) }, ) .post( @@ -118,11 +119,15 @@ export const ProviderRoutes = lazy(() => async (c) => { const providerID = c.req.valid("param").providerID const { method, inputs } = c.req.valid("json") - const result = await ProviderAuth.authorize({ - providerID, - method, - inputs, - }) + const result = await AppRuntime.runPromise( + ProviderAuth.Service.use((svc) => + svc.authorize({ + providerID, + method, + inputs, + }), + ), + ) return c.json(result) }, ) @@ -160,11 +165,15 @@ export const ProviderRoutes = lazy(() => async (c) => { const providerID = c.req.valid("param").providerID const { method, code } = c.req.valid("json") - await ProviderAuth.callback({ - providerID, - method, - code, - }) + await AppRuntime.runPromise( + ProviderAuth.Service.use((svc) => + svc.callback({ + providerID, + method, + code, + }), + ), + ) return c.json(true) }, ), diff --git a/packages/opencode/test/plugin/auth-override.test.ts b/packages/opencode/test/plugin/auth-override.test.ts index 6b77083828..36a02058ea 100644 --- a/packages/opencode/test/plugin/auth-override.test.ts +++ b/packages/opencode/test/plugin/auth-override.test.ts @@ -1,6 +1,7 @@ import { describe, expect, test } from "bun:test" import path from "path" import fs from "fs/promises" +import { Effect } from "effect" import { tmpdir } from "../fixture/fixture" import { Instance } from "../../src/project/instance" import { ProviderAuth } from "../../src/provider/auth" @@ -39,14 +40,18 @@ describe("plugin.auth-override", () => { const methods = await Instance.provide({ directory: tmp.path, fn: async () => { - return ProviderAuth.methods() + return Effect.runPromise( + ProviderAuth.Service.use((svc) => svc.methods()).pipe(Effect.provide(ProviderAuth.defaultLayer)), + ) }, }) const plainMethods = await Instance.provide({ directory: plain.path, fn: async () => { - return ProviderAuth.methods() + return Effect.runPromise( + ProviderAuth.Service.use((svc) => svc.methods()).pipe(Effect.provide(ProviderAuth.defaultLayer)), + ) }, }) From 03ce2e5288c8d2ef58e1197f6200b6be8f2797d7 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Fri, 10 Apr 2026 23:26:16 -0400 Subject: [PATCH 2/6] refactor(installation): drop facade runtime wrappers (#21984) --- packages/opencode/src/cli/cmd/uninstall.ts | 3 +- packages/opencode/src/cli/cmd/upgrade.ts | 11 ++-- packages/opencode/src/cli/upgrade.ts | 7 +-- packages/opencode/src/installation/index.ts | 15 ------ packages/opencode/src/server/routes/global.ts | 54 ++++++++++++------- 5 files changed, 50 insertions(+), 40 deletions(-) diff --git a/packages/opencode/src/cli/cmd/uninstall.ts b/packages/opencode/src/cli/cmd/uninstall.ts index de41f32a0d..31830f0859 100644 --- a/packages/opencode/src/cli/cmd/uninstall.ts +++ b/packages/opencode/src/cli/cmd/uninstall.ts @@ -1,6 +1,7 @@ import type { Argv } from "yargs" import { UI } from "../ui" import * as prompts from "@clack/prompts" +import { AppRuntime } from "@/effect/app-runtime" import { Installation } from "../../installation" import { Global } from "../../global" import fs from "fs/promises" @@ -57,7 +58,7 @@ export const UninstallCommand = { UI.empty() prompts.intro("Uninstall OpenCode") - const method = await Installation.method() + const method = await AppRuntime.runPromise(Installation.Service.use((svc) => svc.method())) prompts.log.info(`Installation method: ${method}`) const targets = await collectRemovalTargets(args, method) diff --git a/packages/opencode/src/cli/cmd/upgrade.ts b/packages/opencode/src/cli/cmd/upgrade.ts index 0182056633..3ffa0f228c 100644 --- a/packages/opencode/src/cli/cmd/upgrade.ts +++ b/packages/opencode/src/cli/cmd/upgrade.ts @@ -1,6 +1,7 @@ import type { Argv } from "yargs" import { UI } from "../ui" import * as prompts from "@clack/prompts" +import { AppRuntime } from "@/effect/app-runtime" import { Installation } from "../../installation" export const UpgradeCommand = { @@ -24,7 +25,7 @@ export const UpgradeCommand = { UI.println(UI.logo(" ")) UI.empty() prompts.intro("Upgrade") - const detectedMethod = await Installation.method() + const detectedMethod = await AppRuntime.runPromise(Installation.Service.use((svc) => svc.method())) const method = (args.method as Installation.Method) ?? detectedMethod if (method === "unknown") { prompts.log.error(`opencode is installed to ${process.execPath} and may be managed by a package manager`) @@ -42,7 +43,9 @@ export const UpgradeCommand = { } } prompts.log.info("Using method: " + method) - const target = args.target ? args.target.replace(/^v/, "") : await Installation.latest() + const target = args.target + ? args.target.replace(/^v/, "") + : await AppRuntime.runPromise(Installation.Service.use((svc) => svc.latest())) if (Installation.VERSION === target) { prompts.log.warn(`opencode upgrade skipped: ${target} is already installed`) @@ -53,7 +56,9 @@ export const UpgradeCommand = { prompts.log.info(`From ${Installation.VERSION} → ${target}`) const spinner = prompts.spinner() spinner.start("Upgrading...") - const err = await Installation.upgrade(method, target).catch((err) => err) + const err = await AppRuntime.runPromise(Installation.Service.use((svc) => svc.upgrade(method, target))).catch( + (err) => err, + ) if (err) { spinner.stop("Upgrade failed", 1) if (err instanceof Installation.UpgradeFailedError) { diff --git a/packages/opencode/src/cli/upgrade.ts b/packages/opencode/src/cli/upgrade.ts index 7b7199d4ea..e902dcb921 100644 --- a/packages/opencode/src/cli/upgrade.ts +++ b/packages/opencode/src/cli/upgrade.ts @@ -1,12 +1,13 @@ import { Bus } from "@/bus" import { Config } from "@/config/config" +import { AppRuntime } from "@/effect/app-runtime" import { Flag } from "@/flag/flag" import { Installation } from "@/installation" export async function upgrade() { const config = await Config.getGlobal() - const method = await Installation.method() - const latest = await Installation.latest(method).catch(() => {}) + const method = await AppRuntime.runPromise(Installation.Service.use((svc) => svc.method())) + const latest = await AppRuntime.runPromise(Installation.Service.use((svc) => svc.latest(method))).catch(() => {}) if (!latest) return if (Flag.OPENCODE_ALWAYS_NOTIFY_UPDATE) { @@ -25,7 +26,7 @@ export async function upgrade() { } if (method === "unknown") return - await Installation.upgrade(method, latest) + await AppRuntime.runPromise(Installation.Service.use((svc) => svc.upgrade(method, latest))) .then(() => Bus.publish(Installation.Event.Updated, { version: latest })) .catch(() => {}) } diff --git a/packages/opencode/src/installation/index.ts b/packages/opencode/src/installation/index.ts index 06b673217f..29f9bf1be2 100644 --- a/packages/opencode/src/installation/index.ts +++ b/packages/opencode/src/installation/index.ts @@ -1,7 +1,6 @@ import { Effect, Layer, Schema, Context, Stream } from "effect" import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http" import * as CrossSpawnSpawner from "@/effect/cross-spawn-spawner" -import { makeRuntime } from "@/effect/run-service" import { withTransientReadRetry } from "@/util/effect-http-client" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" import path from "path" @@ -338,18 +337,4 @@ export namespace Installation { Layer.provide(FetchHttpClient.layer), Layer.provide(CrossSpawnSpawner.defaultLayer), ) - - const { runPromise } = makeRuntime(Service, defaultLayer) - - export async function method(): Promise { - return runPromise((svc) => svc.method()) - } - - export async function latest(installMethod?: Method): Promise { - return runPromise((svc) => svc.latest(installMethod)) - } - - export async function upgrade(m: Method, target: string): Promise { - return runPromise((svc) => svc.upgrade(m, target)) - } } diff --git a/packages/opencode/src/server/routes/global.ts b/packages/opencode/src/server/routes/global.ts index ec7afcf23d..6b0a9a164b 100644 --- a/packages/opencode/src/server/routes/global.ts +++ b/packages/opencode/src/server/routes/global.ts @@ -1,10 +1,12 @@ import { Hono, type Context } from "hono" import { describeRoute, resolver, validator } from "hono-openapi" import { streamSSE } from "hono/streaming" +import { Effect } from "effect" import z from "zod" import { BusEvent } from "@/bus/bus-event" import { SyncEvent } from "@/sync" import { GlobalBus } from "@/bus/global" +import { AppRuntime } from "@/effect/app-runtime" import { AsyncQueue } from "@/util/queue" import { Instance } from "../../project/instance" import { Installation } from "@/installation" @@ -290,25 +292,41 @@ export const GlobalRoutes = lazy(() => }), ), async (c) => { - const method = await Installation.method() - if (method === "unknown") { - return c.json({ success: false, error: "Unknown installation method" }, 400) + const result = await AppRuntime.runPromise( + Installation.Service.use((svc) => + Effect.gen(function* () { + const method = yield* svc.method() + if (method === "unknown") { + return { success: false as const, status: 400 as const, error: "Unknown installation method" } + } + + const target = c.req.valid("json").target || (yield* svc.latest(method)) + const result = yield* Effect.catch( + svc.upgrade(method, target).pipe(Effect.as({ success: true as const, version: target })), + (err) => + Effect.succeed({ + success: false as const, + status: 500 as const, + error: err instanceof Error ? err.message : String(err), + }), + ) + if (!result.success) return result + return { ...result, status: 200 as const } + }), + ), + ) + if (!result.success) { + return c.json({ success: false, error: result.error }, result.status) } - const target = c.req.valid("json").target || (await Installation.latest(method)) - const result = await Installation.upgrade(method, target) - .then(() => ({ success: true as const, version: target })) - .catch((e) => ({ success: false as const, error: e instanceof Error ? e.message : String(e) })) - if (result.success) { - GlobalBus.emit("event", { - directory: "global", - payload: { - type: Installation.Event.Updated.type, - properties: { version: target }, - }, - }) - return c.json(result) - } - return c.json(result, 500) + const target = result.version + GlobalBus.emit("event", { + directory: "global", + payload: { + type: Installation.Event.Updated.type, + properties: { version: target }, + }, + }) + return c.json({ success: true, version: target }) }, ), ) From ba3600a5156c382c3fde73ec029b26f318b7391d Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Fri, 10 Apr 2026 23:27:30 -0400 Subject: [PATCH 3/6] refactor(session): remove dead updatePartDelta facade (#21985) --- packages/opencode/src/session/index.ts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts index 55532a7946..2375d8333d 100644 --- a/packages/opencode/src/session/index.ts +++ b/packages/opencode/src/session/index.ts @@ -851,14 +851,4 @@ export namespace Session { return runPromise((svc) => svc.updatePart(part)) } - export const updatePartDelta = fn( - z.object({ - sessionID: SessionID.zod, - messageID: MessageID.zod, - partID: PartID.zod, - field: z.string(), - delta: z.string(), - }), - (input) => runPromise((svc) => svc.updatePartDelta(input)), - ) } From 3b523b32f5c88141ba50a76e4e6ccab22cee8e5c Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Sat, 11 Apr 2026 03:28:30 +0000 Subject: [PATCH 4/6] chore: generate --- packages/opencode/src/session/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts index 2375d8333d..d7bf99637a 100644 --- a/packages/opencode/src/session/index.ts +++ b/packages/opencode/src/session/index.ts @@ -850,5 +850,4 @@ export namespace Session { MessageV2.Part.parse(part) return runPromise((svc) => svc.updatePart(part)) } - } From 9ca06e033605d3670f1e61b5f31ff0a975f569dc Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Fri, 10 Apr 2026 23:35:50 -0400 Subject: [PATCH 5/6] docs(effect): mark SessionTodo migrated (#21987) --- packages/opencode/specs/effect-migration.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/opencode/specs/effect-migration.md b/packages/opencode/specs/effect-migration.md index 8ada3f7389..b0ce4a3ee1 100644 --- a/packages/opencode/specs/effect-migration.md +++ b/packages/opencode/specs/effect-migration.md @@ -223,7 +223,7 @@ Fully migrated (single namespace, InstanceState where needed, flattened facade): Still open: -- [ ] `SessionTodo` — `session/todo.ts` +- [x] `SessionTodo` — `session/todo.ts` - [ ] `SyncEvent` — `sync/index.ts` - [ ] `Workspace` — `control-plane/workspace.ts` @@ -338,4 +338,5 @@ For each service, the migration is roughly: - `SessionStatus` — migrated 2026-04-11. Replaced the last route and retry-policy callers with `AppRuntime.runPromise(SessionStatus.Service.use(...))` and removed the `makeRuntime(...)` facade. - `ShareNext` — migrated 2026-04-11. Swapped remaining async callers to `AppRuntime.runPromise(ShareNext.Service.use(...))`, removed the `makeRuntime(...)` facade, and kept instance bootstrap on the shared app runtime. +- `SessionTodo` — migrated 2026-04-10. Already matched the target service shape in `session/todo.ts`: single namespace, traced Effect methods, and no `makeRuntime(...)` facade remained; checklist updated to reflect the completed migration. - `Storage` — migrated 2026-04-10. One production caller (`Session.diff`) and all storage.test.ts tests converted to effectful style. Facades and `makeRuntime` removed. From c92c462148a48a9e9496735f7754a69e4a695b31 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Sat, 11 Apr 2026 03:39:49 +0000 Subject: [PATCH 6/6] chore: update nix node_modules hashes --- nix/hashes.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nix/hashes.json b/nix/hashes.json index d827b203d2..13809499e9 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,8 +1,8 @@ { "nodeModules": { - "x86_64-linux": "sha256-285KZ7rZLRoc6XqCZRHc25NE+mmpGh/BVeMpv8aPQtQ=", - "aarch64-linux": "sha256-qIwmY4TP4CI7R7G6A5OMYRrorVNXjkg25tTtVpIHm2o=", - "aarch64-darwin": "sha256-RwvnZQhdYZ0u7h7evyfxuPLHHX9eO/jXTAxIFc8B+IE=", - "x86_64-darwin": "sha256-vVj40al+TEeMpbe5XG2GmJEpN+eQAvtr9W0T98l5PBE=" + "x86_64-linux": "sha256-LkmY8hoc1OkJWnTxberdbo2wKlMTmq1NlyOndHSoR1Y=", + "aarch64-linux": "sha256-TOojgsW0rpYiSuDCV/ZmAuewmkYitB6GBmxus3pXpNo=", + "aarch64-darwin": "sha256-Vf+XYCm3YQQ5HBUK7UDXvEKEDeFUMwlGXQsmzrK+a1E=", + "x86_64-darwin": "sha256-68OlpJhmf5tpapxYGhcYjhx9716X+BFoIHTGosA3Yg4=" } }