diff --git a/packages/opencode/script/seed-e2e.ts b/packages/opencode/script/seed-e2e.ts
index fc3573548d3..33170c3261e 100644
--- a/packages/opencode/script/seed-e2e.ts
+++ b/packages/opencode/script/seed-e2e.ts
@@ -11,7 +11,7 @@ const seed = async () => {
const { Instance } = await import("../src/project/instance")
const { InstanceBootstrap } = await import("../src/project/bootstrap")
const { Config } = await import("../src/config/config")
- const { disposeRuntime } = await import("../src/effect/runtime")
+ const { disposeAllRuntimes } = await import("../src/effect/runtime")
const { Session } = await import("../src/session")
const { MessageID, PartID } = await import("../src/session/schema")
const { Project } = await import("../src/project/project")
@@ -55,7 +55,7 @@ const seed = async () => {
})
} finally {
await Instance.disposeAll().catch(() => {})
- await disposeRuntime().catch(() => {})
+ await disposeAllRuntimes().catch(() => {})
}
}
diff --git a/packages/opencode/src/account/effect.ts b/packages/opencode/src/account/effect.ts
index 2f1304d505b..c5cb0166f9a 100644
--- a/packages/opencode/src/account/effect.ts
+++ b/packages/opencode/src/account/effect.ts
@@ -23,6 +23,7 @@ import {
PollSuccess,
UserCode,
} from "./schema"
+import { makeRuntimeGlobal } from "@/effect/runtime"
export * from "./schema"
@@ -357,4 +358,6 @@ export namespace AccountEffect {
)
export const defaultLayer = layer.pipe(Layer.provide(AccountRepo.layer), Layer.provide(FetchHttpClient.layer))
+
+ export const { runtime, runSync, runPromise } = makeRuntimeGlobal(Service, defaultLayer)
}
diff --git a/packages/opencode/src/account/index.ts b/packages/opencode/src/account/index.ts
index 3a9d758e2f1..4ced47b6182 100644
--- a/packages/opencode/src/account/index.ts
+++ b/packages/opencode/src/account/index.ts
@@ -1,41 +1,24 @@
-import { Effect, Option } from "effect"
+import { Option } from "effect"
-import {
- Account as AccountSchema,
- type AccountError,
- type AccessToken,
- AccountID,
- AccountEffect,
- OrgID,
-} from "./effect"
+import { Account as AccountSchema, type AccessToken, AccountID, OrgID, AccountEffect } from "./effect"
export { AccessToken, AccountID, OrgID } from "./effect"
-import { runtime } from "@/effect/runtime"
-
-function runSync(f: (service: AccountEffect.Interface) => Effect.Effect) {
- return runtime.runSync(AccountEffect.Service.use(f))
-}
-
-function runPromise(f: (service: AccountEffect.Interface) => Effect.Effect) {
- return runtime.runPromise(AccountEffect.Service.use(f))
-}
-
export namespace Account {
export const Account = AccountSchema
export type Account = AccountSchema
export function active(): Account | undefined {
- return Option.getOrUndefined(runSync((service) => service.active()))
+ return Option.getOrUndefined(AccountEffect.runSync((service) => service.active()))
}
export async function config(accountID: AccountID, orgID: OrgID): Promise | undefined> {
- const config = await runPromise((service) => service.config(accountID, orgID))
+ const config = await AccountEffect.runPromise((service) => service.config(accountID, orgID))
return Option.getOrUndefined(config)
}
export async function token(accountID: AccountID): Promise {
- const token = await runPromise((service) => service.token(accountID))
+ const token = await AccountEffect.runPromise((service) => service.token(accountID))
return Option.getOrUndefined(token)
}
}
diff --git a/packages/opencode/src/auth/effect.ts b/packages/opencode/src/auth/effect.ts
index e03ad958674..6c43ea28d27 100644
--- a/packages/opencode/src/auth/effect.ts
+++ b/packages/opencode/src/auth/effect.ts
@@ -1,5 +1,6 @@
import path from "path"
import { Effect, Layer, Record, Result, Schema, ServiceMap } from "effect"
+import { makeRuntimeGlobal } from "@/effect/runtime"
import { Global } from "../global"
import { Filesystem } from "../util/filesystem"
@@ -91,4 +92,6 @@ export namespace AuthEffect {
return Service.of({ get, all, set, remove })
}),
)
+
+ export const { runtime, runSync, runPromise } = makeRuntimeGlobal(Service, layer)
}
diff --git a/packages/opencode/src/auth/index.ts b/packages/opencode/src/auth/index.ts
index 6f588e93751..95f6758a267 100644
--- a/packages/opencode/src/auth/index.ts
+++ b/packages/opencode/src/auth/index.ts
@@ -1,14 +1,8 @@
-import { Effect } from "effect"
import z from "zod"
-import { runtime } from "@/effect/runtime"
import * as S from "./effect"
export { OAUTH_DUMMY_KEY } from "./effect"
-function runPromise(f: (service: S.AuthEffect.Interface) => Effect.Effect) {
- return runtime.runPromise(S.AuthEffect.Service.use(f))
-}
-
export namespace Auth {
export const Oauth = z
.object({
@@ -40,18 +34,18 @@ export namespace Auth {
export type Info = z.infer
export async function get(providerID: string) {
- return runPromise((service) => service.get(providerID))
+ return S.AuthEffect.runPromise((service) => service.get(providerID))
}
export async function all(): Promise> {
- return runPromise((service) => service.all())
+ return S.AuthEffect.runPromise((service) => service.all())
}
export async function set(key: string, info: Info) {
- return runPromise((service) => service.set(key, info))
+ return S.AuthEffect.runPromise((service) => service.set(key, info))
}
export async function remove(key: string) {
- return runPromise((service) => service.remove(key))
+ return S.AuthEffect.runPromise((service) => service.remove(key))
}
}
diff --git a/packages/opencode/src/cli/cmd/account.ts b/packages/opencode/src/cli/cmd/account.ts
index c2b47da11c3..fd66c3fdc99 100644
--- a/packages/opencode/src/cli/cmd/account.ts
+++ b/packages/opencode/src/cli/cmd/account.ts
@@ -1,7 +1,6 @@
import { cmd } from "./cmd"
import { Duration, Effect, Match, Option } from "effect"
import { UI } from "../ui"
-import { runtime } from "@/effect/runtime"
import { AccountID, AccountEffect, OrgID, PollExpired, type PollResult } from "@/account/effect"
import { type AccountError } from "@/account/schema"
import * as Prompt from "../effect/prompt"
@@ -160,7 +159,7 @@ export const LoginCommand = cmd({
}),
async handler(args) {
UI.empty()
- await runtime.runPromise(loginEffect(args.url))
+ await AccountEffect.runtime().runPromise(loginEffect(args.url))
},
})
@@ -174,7 +173,7 @@ export const LogoutCommand = cmd({
}),
async handler(args) {
UI.empty()
- await runtime.runPromise(logoutEffect(args.email))
+ await AccountEffect.runtime().runPromise(logoutEffect(args.email))
},
})
@@ -183,7 +182,7 @@ export const SwitchCommand = cmd({
describe: false,
async handler() {
UI.empty()
- await runtime.runPromise(switchEffect())
+ await AccountEffect.runtime().runPromise(switchEffect())
},
})
@@ -192,7 +191,7 @@ export const OrgsCommand = cmd({
describe: false,
async handler() {
UI.empty()
- await runtime.runPromise(orgsEffect())
+ await AccountEffect.runtime().runPromise(orgsEffect())
},
})
diff --git a/packages/opencode/src/effect/instance-registry.ts b/packages/opencode/src/effect/instance-registry.ts
deleted file mode 100644
index 59c556e0447..00000000000
--- a/packages/opencode/src/effect/instance-registry.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-const disposers = new Set<(directory: string) => Promise>()
-
-export function registerDisposer(disposer: (directory: string) => Promise) {
- disposers.add(disposer)
- return () => {
- disposers.delete(disposer)
- }
-}
-
-export async function disposeInstance(directory: string) {
- await Promise.allSettled([...disposers].map((disposer) => disposer(directory)))
-}
diff --git a/packages/opencode/src/effect/instances.ts b/packages/opencode/src/effect/instances.ts
deleted file mode 100644
index c05458d5df9..00000000000
--- a/packages/opencode/src/effect/instances.ts
+++ /dev/null
@@ -1,68 +0,0 @@
-import { Effect, Layer, LayerMap, ServiceMap } from "effect"
-import { File } from "@/file"
-import { FileTime } from "@/file/time"
-import { FileWatcher } from "@/file/watcher"
-import { Format } from "@/format"
-import { PermissionNext } from "@/permission"
-import { Instance } from "@/project/instance"
-import { Vcs } from "@/project/vcs"
-import { ProviderAuth } from "@/provider/auth"
-import { Question } from "@/question"
-import { Skill } from "@/skill/skill"
-import { Snapshot } from "@/snapshot"
-import { InstanceContext } from "./instance-context"
-import { registerDisposer } from "./instance-registry"
-
-export { InstanceContext } from "./instance-context"
-
-export type InstanceServices =
- | Question.Service
- | PermissionNext.Service
- | ProviderAuth.Service
- | FileWatcher.Service
- | Vcs.Service
- | FileTime.Service
- | Format.Service
- | File.Service
- | Skill.Service
- | Snapshot.Service
-
-// NOTE: LayerMap only passes the key (directory string) to lookup, but we need
-// the full instance context (directory, worktree, project). We read from the
-// legacy Instance ALS here, which is safe because lookup is only triggered via
-// runPromiseInstance -> Instances.get, which always runs inside Instance.provide.
-// This should go away once the old Instance type is removed and lookup can load
-// the full context directly.
-function lookup(_key: string) {
- const ctx = Layer.sync(InstanceContext, () => InstanceContext.of(Instance.current))
- return Layer.mergeAll(
- Layer.fresh(Question.layer),
- Layer.fresh(PermissionNext.layer),
- Layer.fresh(ProviderAuth.defaultLayer),
- Layer.fresh(FileWatcher.layer).pipe(Layer.orDie),
- Layer.fresh(Vcs.layer),
- Layer.fresh(FileTime.layer).pipe(Layer.orDie),
- Layer.fresh(Format.layer),
- Layer.fresh(File.layer),
- Layer.fresh(Skill.defaultLayer),
- Layer.fresh(Snapshot.defaultLayer),
- ).pipe(Layer.provide(ctx))
-}
-
-export class Instances extends ServiceMap.Service>()(
- "opencode/Instances",
-) {
- static readonly layer = Layer.effect(
- Instances,
- Effect.gen(function* () {
- const layerMap = yield* LayerMap.make(lookup, { idleTimeToLive: Infinity })
- const unregister = registerDisposer((directory) => Effect.runPromise(layerMap.invalidate(directory)))
- yield* Effect.addFinalizer(() => Effect.sync(unregister))
- return Instances.of(layerMap)
- }),
- )
-
- static get(directory: string): Layer.Layer {
- return Layer.unwrap(Instances.use((map) => Effect.succeed(map.get(directory))))
- }
-}
diff --git a/packages/opencode/src/effect/runtime.ts b/packages/opencode/src/effect/runtime.ts
index f52203b2220..9b34eaca6c8 100644
--- a/packages/opencode/src/effect/runtime.ts
+++ b/packages/opencode/src/effect/runtime.ts
@@ -1,23 +1,103 @@
import { Effect, Layer, ManagedRuntime } from "effect"
-import { AccountEffect } from "@/account/effect"
-import { AuthEffect } from "@/auth/effect"
-import { Instances } from "@/effect/instances"
-import type { InstanceServices } from "@/effect/instances"
-import { TruncateEffect } from "@/tool/truncate-effect"
+import * as ServiceMap from "effect/ServiceMap"
+import { InstanceContext } from "./instance-context"
import { Instance } from "@/project/instance"
+import * as Exit from "effect/Exit"
+import * as Cause from "effect/Cause"
-export const runtime = ManagedRuntime.make(
- Layer.mergeAll(
- AccountEffect.defaultLayer, //
- TruncateEffect.defaultLayer,
- Instances.layer,
- ).pipe(Layer.provideMerge(AuthEffect.layer)),
-)
+export const opencodeMemoMap = Layer.makeMemoMapUnsafe()
-export function runPromiseInstance(effect: Effect.Effect) {
- return runtime.runPromise(effect.pipe(Effect.provide(Instances.get(Instance.directory))))
+export interface ServiceRuntime {
+ readonly runtime: () => ManagedRuntime.ManagedRuntime
+ readonly runSync: (f: (service: S) => Effect.Effect) => A
+ readonly runPromise: (f: (service: S) => Effect.Effect, options?: Effect.RunOptions) => Promise
}
-export function disposeRuntime() {
- return runtime.dispose()
+export const makeRuntimeGlobal = (
+ service: ServiceMap.Service,
+ layer: Layer.Layer,
+): ServiceRuntime => {
+ let runtime = globalRuntimes.get(layer) as ManagedRuntime.ManagedRuntime | undefined
+ if (!runtime) {
+ runtime = ManagedRuntime.make(layer, { memoMap: opencodeMemoMap })
+ globalRuntimes.set(layer, runtime)
+ }
+ const runSync = (f: (service: S) => Effect.Effect) => runtime.runSync(service.use(f))
+ const runPromise = (f: (service: S) => Effect.Effect) => runtime.runPromise(service.use(f))
+ return { runtime: () => runtime, runSync, runPromise }
+}
+const globalRuntimes = new Map, ManagedRuntime.ManagedRuntime>()
+
+export const makeRuntimeInstance = (
+ service: ServiceMap.Service,
+ layer: Layer.Layer,
+): ServiceRuntime => {
+ const runSync = (f: (service: S) => Effect.Effect) => getInstanceRuntime(layer).runSync(service.use(f))
+ const runPromise = (f: (service: S) => Effect.Effect, options?: Effect.RunOptions) =>
+ new Promise((resolve, reject) => {
+ const fiber = getInstanceRuntime(layer).runFork(service.use(f), options)
+ fiber.addObserver((exit) => {
+ if (Exit.isSuccess(exit)) {
+ return resolve(exit.value)
+ } else if (Cause.hasInterruptsOnly(exit.cause)) {
+ return
+ }
+ reject(Cause.squash(exit.cause))
+ })
+ })
+ return { runtime: () => getInstanceRuntime(layer), runSync, runPromise }
+}
+
+const allRuntimes = new Map, ManagedRuntime.ManagedRuntime>>()
+
+const getInstanceRuntime = (
+ layer: Layer.Layer,
+): ManagedRuntime.ManagedRuntime => {
+ const directory = Instance.directory
+
+ let map = allRuntimes.get(directory)
+ if (!map) {
+ map = new Map()
+ allRuntimes.set(directory, map)
+ }
+
+ let runtime = map.get(layer) as ManagedRuntime.ManagedRuntime | undefined
+ if (!runtime) {
+ runtime = ManagedRuntime.make(
+ Layer.provideMerge(
+ layer,
+ Layer.sync(InstanceContext, () => InstanceContext.of(Instance.current)),
+ ),
+ { memoMap: opencodeMemoMap },
+ )
+ map.set(layer, runtime)
+ }
+
+ return runtime
+}
+
+export async function disposeAllRuntimes() {
+ let promises: Promise[] = []
+ for (const runtime of globalRuntimes.values()) {
+ promises.push(runtime.dispose())
+ }
+ globalRuntimes.clear()
+ for (const map of allRuntimes.values()) {
+ for (const runtime of map.values()) {
+ promises.push(runtime.dispose())
+ }
+ }
+ allRuntimes.clear()
+ await Promise.all(promises)
+}
+
+export const disposeInstanceRuntimes = async (directory: string) => {
+ const map = allRuntimes.get(directory)
+ if (!map) return
+ let promises: Promise[] = []
+ for (const runtime of map.values()) {
+ promises.push(runtime.dispose())
+ }
+ allRuntimes.delete(directory)
+ await Promise.all(promises)
}
diff --git a/packages/opencode/src/file/index.ts b/packages/opencode/src/file/index.ts
index 6e9b917271f..c73e3dbf89e 100644
--- a/packages/opencode/src/file/index.ts
+++ b/packages/opencode/src/file/index.ts
@@ -1,6 +1,6 @@
import { BusEvent } from "@/bus/bus-event"
import { InstanceContext } from "@/effect/instance-context"
-import { runPromiseInstance } from "@/effect/runtime"
+import { makeRuntimeInstance } from "@/effect/runtime"
import { git } from "@/util/git"
import { Effect, Fiber, Layer, Scope, ServiceMap } from "effect"
import { formatPatch, structuredPatch } from "diff"
@@ -84,23 +84,23 @@ export namespace File {
}
export function init() {
- return runPromiseInstance(Service.use((svc) => svc.init()))
+ return runPromise((svc) => svc.init())
}
export async function status() {
- return runPromiseInstance(Service.use((svc) => svc.status()))
+ return runPromise((svc) => svc.status())
}
export async function read(file: string): Promise {
- return runPromiseInstance(Service.use((svc) => svc.read(file)))
+ return runPromise((svc) => svc.read(file))
}
export async function list(dir?: string) {
- return runPromiseInstance(Service.use((svc) => svc.list(dir)))
+ return runPromise((svc) => svc.list(dir))
}
export async function search(input: { query: string; limit?: number; dirs?: boolean; type?: "file" | "directory" }) {
- return runPromiseInstance(Service.use((svc) => svc.search(input)))
+ return runPromise((svc) => svc.search(input))
}
const log = Log.create({ service: "file" })
@@ -692,4 +692,6 @@ export namespace File {
return Service.of({ init, status, read, list, search })
}),
)
+
+ export const { runtime, runSync, runPromise } = makeRuntimeInstance(Service, Layer.fresh(layer))
}
diff --git a/packages/opencode/src/file/time.ts b/packages/opencode/src/file/time.ts
index 3d94bc12221..1f5f07ca9f2 100644
--- a/packages/opencode/src/file/time.ts
+++ b/packages/opencode/src/file/time.ts
@@ -1,5 +1,5 @@
import { DateTime, Effect, Layer, Semaphore, ServiceMap } from "effect"
-import { runPromiseInstance } from "@/effect/runtime"
+import { makeRuntimeInstance } from "@/effect/runtime"
import { Flag } from "@/flag/flag"
import type { SessionID } from "@/session/schema"
import { Filesystem } from "../util/filesystem"
@@ -92,19 +92,21 @@ export namespace FileTime {
}),
)
+ export const { runtime, runSync, runPromise } = makeRuntimeInstance(Service, Layer.fresh(layer))
+
export function read(sessionID: SessionID, file: string) {
- return runPromiseInstance(Service.use((s) => s.read(sessionID, file)))
+ return runPromise((s) => s.read(sessionID, file))
}
export function get(sessionID: SessionID, file: string) {
- return runPromiseInstance(Service.use((s) => s.get(sessionID, file)))
+ return runPromise((s) => s.get(sessionID, file))
}
export async function assert(sessionID: SessionID, filepath: string) {
- return runPromiseInstance(Service.use((s) => s.assert(sessionID, filepath)))
+ return runPromise((s) => s.assert(sessionID, filepath))
}
export async function withLock(filepath: string, fn: () => Promise): Promise {
- return runPromiseInstance(Service.use((s) => s.withLock(filepath, fn)))
+ return runPromise((s) => s.withLock(filepath, fn))
}
}
diff --git a/packages/opencode/src/format/index.ts b/packages/opencode/src/format/index.ts
index 6da8caa08c8..db82e613c86 100644
--- a/packages/opencode/src/format/index.ts
+++ b/packages/opencode/src/format/index.ts
@@ -1,5 +1,4 @@
import { Effect, Layer, ServiceMap } from "effect"
-import { runPromiseInstance } from "@/effect/runtime"
import { InstanceContext } from "@/effect/instance-context"
import path from "path"
import { mergeDeep } from "remeda"
@@ -11,6 +10,7 @@ import { Instance } from "../project/instance"
import { Process } from "../util/process"
import { Log } from "../util/log"
import * as Formatter from "./formatter"
+import { makeRuntimeInstance } from "@/effect/runtime"
export namespace Format {
const log = Log.create({ service: "format" })
@@ -151,7 +151,9 @@ export namespace Format {
}),
)
+ export const { runtime, runSync, runPromise } = makeRuntimeInstance(Service, Layer.fresh(layer))
+
export async function status() {
- return runPromiseInstance(Service.use((s) => s.status()))
+ return runPromise((s) => s.status())
}
}
diff --git a/packages/opencode/src/permission/index.ts b/packages/opencode/src/permission/index.ts
index 93a8c49b655..f82a75ee333 100644
--- a/packages/opencode/src/permission/index.ts
+++ b/packages/opencode/src/permission/index.ts
@@ -1,4 +1,3 @@
-import { runPromiseInstance } from "@/effect/runtime"
import { Bus } from "@/bus"
import { BusEvent } from "@/bus/bus-event"
import { Config } from "@/config/config"
@@ -14,6 +13,7 @@ import { Deferred, Effect, Layer, Schema, ServiceMap } from "effect"
import os from "os"
import z from "zod"
import { PermissionID } from "./schema"
+import { makeRuntimeInstance } from "@/effect/runtime"
export namespace PermissionNext {
const log = Log.create({ service: "permission" })
@@ -246,6 +246,8 @@ export namespace PermissionNext {
}),
)
+ export const { runtime, runSync, runPromise } = makeRuntimeInstance(Service, Layer.fresh(layer))
+
function expand(pattern: string): string {
if (pattern.startsWith("~/")) return os.homedir() + pattern.slice(1)
if (pattern === "~") return os.homedir()
@@ -272,12 +274,12 @@ export namespace PermissionNext {
return rulesets.flat()
}
- export const ask = fn(AskInput, async (input) => runPromiseInstance(Service.use((svc) => svc.ask(input))))
+ export const ask = fn(AskInput, async (input) => runPromise((svc) => svc.ask(input)))
- export const reply = fn(ReplyInput, async (input) => runPromiseInstance(Service.use((svc) => svc.reply(input))))
+ export const reply = fn(ReplyInput, async (input) => runPromise((svc) => svc.reply(input)))
export async function list() {
- return runPromiseInstance(Service.use((svc) => svc.list()))
+ return runPromise((svc) => svc.list())
}
const EDIT_TOOLS = ["edit", "write", "apply_patch", "multiedit"]
diff --git a/packages/opencode/src/project/instance.ts b/packages/opencode/src/project/instance.ts
index 6075540161b..680ff5b788f 100644
--- a/packages/opencode/src/project/instance.ts
+++ b/packages/opencode/src/project/instance.ts
@@ -1,11 +1,11 @@
import { GlobalBus } from "@/bus/global"
-import { disposeInstance } from "@/effect/instance-registry"
import { Filesystem } from "@/util/filesystem"
import { iife } from "@/util/iife"
import { Log } from "@/util/log"
import { Context } from "../util/context"
import { Project } from "./project"
import { State } from "./state"
+import { disposeInstanceRuntimes } from "@/effect/runtime"
interface Context {
directory: string
@@ -119,7 +119,7 @@ export const Instance = {
async reload(input: { directory: string; init?: () => Promise; project?: Project.Info; worktree?: string }) {
const directory = Filesystem.resolve(input.directory)
Log.Default.info("reloading instance", { directory })
- await Promise.all([State.dispose(directory), disposeInstance(directory)])
+ await Promise.all([State.dispose(directory), disposeInstanceRuntimes(directory)])
cache.delete(directory)
const next = track(directory, boot({ ...input, directory }))
emit(directory)
@@ -128,7 +128,7 @@ export const Instance = {
async dispose() {
const directory = Instance.directory
Log.Default.info("disposing instance", { directory })
- await Promise.all([State.dispose(directory), disposeInstance(directory)])
+ await Promise.all([State.dispose(directory), disposeInstanceRuntimes(directory)])
cache.delete(directory)
emit(directory)
},
diff --git a/packages/opencode/src/project/vcs.ts b/packages/opencode/src/project/vcs.ts
index 9e85571c497..473547a6112 100644
--- a/packages/opencode/src/project/vcs.ts
+++ b/packages/opencode/src/project/vcs.ts
@@ -1,4 +1,5 @@
import { Effect, Layer, ServiceMap } from "effect"
+import { makeRuntimeInstance } from "@/effect/runtime"
import { Bus } from "@/bus"
import { BusEvent } from "@/bus/bus-event"
import { InstanceContext } from "@/effect/instance-context"
@@ -80,4 +81,6 @@ export namespace Vcs {
})
}),
)
+
+ export const { runtime, runSync, runPromise } = makeRuntimeInstance(Service, Layer.fresh(layer))
}
diff --git a/packages/opencode/src/provider/auth.ts b/packages/opencode/src/provider/auth.ts
index 5204b5fb8d3..886495ae015 100644
--- a/packages/opencode/src/provider/auth.ts
+++ b/packages/opencode/src/provider/auth.ts
@@ -1,11 +1,11 @@
import type { AuthOuathResult } from "@opencode-ai/plugin"
import { NamedError } from "@opencode-ai/util/error"
import * as Auth from "@/auth/effect"
-import { runPromiseInstance } from "@/effect/runtime"
import { fn } from "@/util/fn"
import { ProviderID } from "./schema"
-import { Array as Arr, Effect, Layer, Record, Result, ServiceMap, Struct } from "effect"
+import { Array as Arr, Effect, Layer, Record, Result, ServiceMap } from "effect"
import z from "zod"
+import { makeRuntimeGlobal } from "@/effect/runtime"
export namespace ProviderAuth {
export const Method = z
@@ -215,8 +215,10 @@ export namespace ProviderAuth {
export const defaultLayer = layer.pipe(Layer.provide(Auth.AuthEffect.layer))
+ export const { runtime, runSync, runPromise } = makeRuntimeGlobal(Service, defaultLayer)
+
export async function methods() {
- return runPromiseInstance(Service.use((svc) => svc.methods()))
+ return runPromise((svc) => svc.methods())
}
export const authorize = fn(
@@ -225,7 +227,7 @@ export namespace ProviderAuth {
method: z.number(),
inputs: z.record(z.string(), z.string()).optional(),
}),
- async (input): Promise => runPromiseInstance(Service.use((svc) => svc.authorize(input))),
+ async (input): Promise => runPromise((svc) => svc.authorize(input)),
)
export const callback = fn(
@@ -234,6 +236,6 @@ export namespace ProviderAuth {
method: z.number(),
code: z.string().optional(),
}),
- async (input) => runPromiseInstance(Service.use((svc) => svc.callback(input))),
+ async (input) => runPromise((svc) => svc.callback(input)),
)
}
diff --git a/packages/opencode/src/question/index.ts b/packages/opencode/src/question/index.ts
index 551c5139999..514a4eab7e7 100644
--- a/packages/opencode/src/question/index.ts
+++ b/packages/opencode/src/question/index.ts
@@ -1,5 +1,5 @@
import { Deferred, Effect, Layer, Schema, ServiceMap } from "effect"
-import { runPromiseInstance } from "@/effect/runtime"
+import { makeRuntimeInstance } from "@/effect/runtime"
import { Bus } from "@/bus"
import { BusEvent } from "@/bus/bus-event"
import { SessionID, MessageID } from "@/session/schema"
@@ -171,23 +171,25 @@ export namespace Question {
}),
)
+ export const { runtime, runSync, runPromise } = makeRuntimeInstance(Service, Layer.fresh(layer))
+
export async function ask(input: {
sessionID: SessionID
questions: Info[]
tool?: { messageID: MessageID; callID: string }
}): Promise {
- return runPromiseInstance(Service.use((svc) => svc.ask(input)))
+ return runPromise((svc) => svc.ask(input))
}
export async function reply(input: { requestID: QuestionID; answers: Answer[] }): Promise {
- return runPromiseInstance(Service.use((svc) => svc.reply(input)))
+ return runPromise((svc) => svc.reply(input))
}
export async function reject(requestID: QuestionID): Promise {
- return runPromiseInstance(Service.use((svc) => svc.reject(requestID)))
+ return runPromise((svc) => svc.reject(requestID))
}
export async function list(): Promise {
- return runPromiseInstance(Service.use((svc) => svc.list()))
+ return runPromise((svc) => svc.list())
}
}
diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts
index a68becb1fba..d156e99b282 100644
--- a/packages/opencode/src/server/server.ts
+++ b/packages/opencode/src/server/server.ts
@@ -12,7 +12,6 @@ import { Format } from "../format"
import { TuiRoutes } from "./routes/tui"
import { Instance } from "../project/instance"
import { Vcs } from "../project/vcs"
-import { runPromiseInstance } from "@/effect/runtime"
import { Agent } from "../agent/agent"
import { Skill } from "../skill/skill"
import { Auth } from "../auth"
@@ -331,7 +330,7 @@ export namespace Server {
},
}),
async (c) => {
- const branch = await runPromiseInstance(Vcs.Service.use((s) => s.branch()))
+ const branch = await Vcs.runPromise((s) => s.branch())
return c.json({
branch,
})
diff --git a/packages/opencode/src/skill/skill.ts b/packages/opencode/src/skill/skill.ts
index 5339691a01b..fdd2ee66a77 100644
--- a/packages/opencode/src/skill/skill.ts
+++ b/packages/opencode/src/skill/skill.ts
@@ -7,7 +7,6 @@ import { NamedError } from "@opencode-ai/util/error"
import type { Agent } from "@/agent/agent"
import { Bus } from "@/bus"
import { InstanceContext } from "@/effect/instance-context"
-import { runPromiseInstance } from "@/effect/runtime"
import { Flag } from "@/flag/flag"
import { Global } from "@/global"
import { PermissionNext } from "@/permission"
@@ -17,6 +16,7 @@ import { ConfigMarkdown } from "../config/markdown"
import { Glob } from "../util/glob"
import { Log } from "../util/log"
import { Discovery } from "./discovery"
+import { makeRuntimeInstance } from "@/effect/runtime"
export namespace Skill {
const log = Log.create({ service: "skill" })
@@ -214,23 +214,26 @@ export namespace Skill {
)
export const defaultLayer: Layer.Layer = layer.pipe(
+ Layer.fresh,
Layer.provide(Discovery.defaultLayer),
)
+ export const { runtime, runSync, runPromise } = makeRuntimeInstance(Service, defaultLayer)
+
export async function get(name: string) {
- return runPromiseInstance(Service.use((skill) => skill.get(name)))
+ return runPromise((skill) => skill.get(name))
}
export async function all() {
- return runPromiseInstance(Service.use((skill) => skill.all()))
+ return runPromise((skill) => skill.all())
}
export async function dirs() {
- return runPromiseInstance(Service.use((skill) => skill.dirs()))
+ return runPromise((skill) => skill.dirs())
}
export async function available(agent?: Agent.Info) {
- return runPromiseInstance(Service.use((skill) => skill.available(agent)))
+ return runPromise((skill) => skill.available(agent))
}
export function fmt(list: Info[], opts: { verbose: boolean }) {
diff --git a/packages/opencode/src/snapshot/index.ts b/packages/opencode/src/snapshot/index.ts
index 887bce33416..91417d2f9a0 100644
--- a/packages/opencode/src/snapshot/index.ts
+++ b/packages/opencode/src/snapshot/index.ts
@@ -4,11 +4,11 @@ import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
import path from "path"
import z from "zod"
import { InstanceContext } from "@/effect/instance-context"
-import { runPromiseInstance } from "@/effect/runtime"
import { AppFileSystem } from "@/filesystem"
import { Config } from "../config/config"
import { Global } from "../global"
import { Log } from "../util/log"
+import { makeRuntimeInstance } from "@/effect/runtime"
export namespace Snapshot {
export const Patch = z.object({
@@ -32,31 +32,31 @@ export namespace Snapshot {
export type FileDiff = z.infer
export async function cleanup() {
- return runPromiseInstance(Service.use((svc) => svc.cleanup()))
+ return runPromise((svc) => svc.cleanup())
}
export async function track() {
- return runPromiseInstance(Service.use((svc) => svc.track()))
+ return runPromise((svc) => svc.track())
}
export async function patch(hash: string) {
- return runPromiseInstance(Service.use((svc) => svc.patch(hash)))
+ return runPromise((svc) => svc.patch(hash))
}
export async function restore(snapshot: string) {
- return runPromiseInstance(Service.use((svc) => svc.restore(snapshot)))
+ return runPromise((svc) => svc.restore(snapshot))
}
export async function revert(patches: Patch[]) {
- return runPromiseInstance(Service.use((svc) => svc.revert(patches)))
+ return runPromise((svc) => svc.revert(patches))
}
export async function diff(hash: string) {
- return runPromiseInstance(Service.use((svc) => svc.diff(hash)))
+ return runPromise((svc) => svc.diff(hash))
}
export async function diffFull(from: string, to: string) {
- return runPromiseInstance(Service.use((svc) => svc.diffFull(from, to)))
+ return runPromise((svc) => svc.diffFull(from, to))
}
const log = Log.create({ service: "snapshot" })
@@ -341,9 +341,12 @@ export namespace Snapshot {
)
export const defaultLayer = layer.pipe(
+ Layer.fresh,
Layer.provide(NodeChildProcessSpawner.layer),
Layer.provide(AppFileSystem.defaultLayer),
Layer.provide(NodeFileSystem.layer), // needed by NodeChildProcessSpawner
Layer.provide(NodePath.layer),
)
+
+ export const { runtime, runSync, runPromise } = makeRuntimeInstance(Service, defaultLayer)
}
diff --git a/packages/opencode/src/tool/truncate-effect.ts b/packages/opencode/src/tool/truncate-effect.ts
index 4431c18f839..21c6b74b9b0 100644
--- a/packages/opencode/src/tool/truncate-effect.ts
+++ b/packages/opencode/src/tool/truncate-effect.ts
@@ -8,6 +8,7 @@ import { Identifier } from "../id/id"
import { Log } from "../util/log"
import { ToolID } from "./schema"
import { TRUNCATION_DIR } from "./truncation-dir"
+import { makeRuntimeGlobal } from "@/effect/runtime"
export namespace TruncateEffect {
const log = Log.create({ service: "truncation" })
@@ -134,4 +135,6 @@ export namespace TruncateEffect {
)
export const defaultLayer = layer.pipe(Layer.provide(AppFileSystem.defaultLayer), Layer.provide(NodePath.layer))
+
+ export const { runtime, runSync, runPromise } = makeRuntimeGlobal(Service, defaultLayer)
}
diff --git a/packages/opencode/src/tool/truncate.ts b/packages/opencode/src/tool/truncate.ts
index 159b2d1d5b7..c1b9f6b747f 100644
--- a/packages/opencode/src/tool/truncate.ts
+++ b/packages/opencode/src/tool/truncate.ts
@@ -1,5 +1,4 @@
import type { Agent } from "../agent/agent"
-import { runtime } from "@/effect/runtime"
import { TruncateEffect as S } from "./truncate-effect"
export namespace Truncate {
@@ -13,6 +12,6 @@ export namespace Truncate {
export type Options = S.Options
export async function output(text: string, options: Options = {}, agent?: Agent.Info): Promise {
- return runtime.runPromise(S.Service.use((s) => s.output(text, options, agent)))
+ return S.runPromise((s) => s.output(text, options, agent))
}
}
diff --git a/packages/opencode/test/permission/next.test.ts b/packages/opencode/test/permission/next.test.ts
index 2a6b6e0bafa..70171b4785d 100644
--- a/packages/opencode/test/permission/next.test.ts
+++ b/packages/opencode/test/permission/next.test.ts
@@ -2,8 +2,6 @@ import { afterEach, test, expect } from "bun:test"
import os from "os"
import { Effect } from "effect"
import { Bus } from "../../src/bus"
-import { runtime } from "../../src/effect/runtime"
-import { Instances } from "../../src/effect/instances"
import { PermissionNext } from "../../src/permission"
import { PermissionNext as S } from "../../src/permission"
import { PermissionID } from "../../src/permission/schema"
@@ -1004,8 +1002,8 @@ test("ask - abort should clear pending request", async () => {
directory: tmp.path,
fn: async () => {
const ctl = new AbortController()
- const ask = runtime.runPromise(
- S.Service.use((svc) =>
+ const ask = PermissionNext.runPromise(
+ (svc) =>
svc.ask({
sessionID: SessionID.make("session_test"),
permission: "bash",
@@ -1014,7 +1012,6 @@ test("ask - abort should clear pending request", async () => {
always: [],
ruleset: [{ permission: "bash", pattern: "*", action: "ask" }],
}),
- ).pipe(Effect.provide(Instances.get(Instance.directory))),
{ signal: ctl.signal },
)