summaryrefslogtreecommitdiffhomepage
path: root/packages
diff options
context:
space:
mode:
authorKit Langton <[email protected]>2026-04-01 12:13:13 -0400
committerGitHub <[email protected]>2026-04-01 16:13:13 +0000
commit2f405daa983c950794aa3982584f59411f89bc50 (patch)
treee33511b889de861f1584d3d49dd27061a2ba890c /packages
parenta9c85b7c2789f9363cbfeb9c1adceaddfbdbbdc3 (diff)
downloadopencode-2f405daa983c950794aa3982584f59411f89bc50.tar.gz
opencode-2f405daa983c950794aa3982584f59411f89bc50.zip
refactor: use Effect services instead of async facades in provider, auth, and file (#20480)
Diffstat (limited to 'packages')
-rw-r--r--packages/opencode/src/effect/instance-state.ts8
-rw-r--r--packages/opencode/src/file/index.ts111
-rw-r--r--packages/opencode/src/provider/auth.ts33
-rw-r--r--packages/opencode/src/provider/provider.ts9
4 files changed, 79 insertions, 82 deletions
diff --git a/packages/opencode/src/effect/instance-state.ts b/packages/opencode/src/effect/instance-state.ts
index b073cf0a4..cc5901fb5 100644
--- a/packages/opencode/src/effect/instance-state.ts
+++ b/packages/opencode/src/effect/instance-state.ts
@@ -24,9 +24,9 @@ export namespace InstanceState {
return ((...args: any[]) => Instance.restore(ctx, () => fn(...args))) as F
}
- export const context = Effect.fnUntraced(function* () {
+ export const context = Effect.gen(function* () {
return (yield* InstanceRef) ?? Instance.current
- })()
+ })
export const directory = Effect.map(context, (ctx) => ctx.directory)
@@ -37,9 +37,9 @@ export namespace InstanceState {
const cache = yield* ScopedCache.make<string, A, E, R>({
capacity: Number.POSITIVE_INFINITY,
lookup: () =>
- Effect.fnUntraced(function* () {
+ Effect.gen(function* () {
return yield* init(yield* context)
- })(),
+ }),
})
const off = registerDisposer((directory) => Effect.runPromise(ScopedCache.invalidate(cache, directory)))
diff --git a/packages/opencode/src/file/index.ts b/packages/opencode/src/file/index.ts
index 08b2faf6b..353f02c31 100644
--- a/packages/opencode/src/file/index.ts
+++ b/packages/opencode/src/file/index.ts
@@ -5,7 +5,6 @@ import { AppFileSystem } from "@/filesystem"
import { git } from "@/util/git"
import { Effect, Layer, ServiceMap } from "effect"
import { formatPatch, structuredPatch } from "diff"
-import fs from "fs"
import fuzzysort from "fuzzysort"
import ignore from "ignore"
import path from "path"
@@ -359,49 +358,46 @@ export namespace File {
const isGlobalHome = Instance.directory === Global.Path.home && Instance.project.id === "global"
const next: Entry = { files: [], dirs: [] }
- yield* Effect.promise(async () => {
- if (isGlobalHome) {
- const dirs = new Set<string>()
- const protectedNames = Protected.names()
- const ignoreNested = new Set(["node_modules", "dist", "build", "target", "vendor"])
- const shouldIgnoreName = (name: string) => name.startsWith(".") || protectedNames.has(name)
- const shouldIgnoreNested = (name: string) => name.startsWith(".") || ignoreNested.has(name)
- const top = await fs.promises
- .readdir(Instance.directory, { withFileTypes: true })
- .catch(() => [] as fs.Dirent[])
-
- for (const entry of top) {
- if (!entry.isDirectory()) continue
- if (shouldIgnoreName(entry.name)) continue
- dirs.add(entry.name + "/")
-
- const base = path.join(Instance.directory, entry.name)
- const children = await fs.promises.readdir(base, { withFileTypes: true }).catch(() => [] as fs.Dirent[])
- for (const child of children) {
- if (!child.isDirectory()) continue
- if (shouldIgnoreNested(child.name)) continue
- dirs.add(entry.name + "/" + child.name + "/")
- }
+ if (isGlobalHome) {
+ const dirs = new Set<string>()
+ const protectedNames = Protected.names()
+ const ignoreNested = new Set(["node_modules", "dist", "build", "target", "vendor"])
+ const shouldIgnoreName = (name: string) => name.startsWith(".") || protectedNames.has(name)
+ const shouldIgnoreNested = (name: string) => name.startsWith(".") || ignoreNested.has(name)
+ const top = yield* appFs.readDirectoryEntries(Instance.directory).pipe(Effect.orElseSucceed(() => []))
+
+ for (const entry of top) {
+ if (entry.type !== "directory") continue
+ if (shouldIgnoreName(entry.name)) continue
+ dirs.add(entry.name + "/")
+
+ const base = path.join(Instance.directory, entry.name)
+ const children = yield* appFs.readDirectoryEntries(base).pipe(Effect.orElseSucceed(() => []))
+ for (const child of children) {
+ if (child.type !== "directory") continue
+ if (shouldIgnoreNested(child.name)) continue
+ dirs.add(entry.name + "/" + child.name + "/")
}
+ }
- next.dirs = Array.from(dirs).toSorted()
- } else {
- const seen = new Set<string>()
- for await (const file of Ripgrep.files({ cwd: Instance.directory })) {
- next.files.push(file)
- let current = file
- while (true) {
- const dir = path.dirname(current)
- if (dir === ".") break
- if (dir === current) break
- current = dir
- if (seen.has(dir)) continue
- seen.add(dir)
- next.dirs.push(dir + "/")
- }
+ next.dirs = Array.from(dirs).toSorted()
+ } else {
+ const files = yield* Effect.promise(() => Array.fromAsync(Ripgrep.files({ cwd: Instance.directory })))
+ const seen = new Set<string>()
+ for (const file of files) {
+ next.files.push(file)
+ let current = file
+ while (true) {
+ const dir = path.dirname(current)
+ if (dir === ".") break
+ if (dir === current) break
+ current = dir
+ if (seen.has(dir)) continue
+ seen.add(dir)
+ next.dirs.push(dir + "/")
}
}
- })
+ }
const s = yield* InstanceState.get(state)
s.cache = next
@@ -636,30 +632,27 @@ export namespace File {
yield* ensure()
const { cache } = yield* InstanceState.get(state)
- return yield* Effect.promise(async () => {
- const query = input.query.trim()
- const limit = input.limit ?? 100
- const kind = input.type ?? (input.dirs === false ? "file" : "all")
- log.info("search", { query, kind })
+ const query = input.query.trim()
+ const limit = input.limit ?? 100
+ const kind = input.type ?? (input.dirs === false ? "file" : "all")
+ log.info("search", { query, kind })
- const result = cache
- const preferHidden = query.startsWith(".") || query.includes("/.")
+ const preferHidden = query.startsWith(".") || query.includes("/.")
- if (!query) {
- if (kind === "file") return result.files.slice(0, limit)
- return sortHiddenLast(result.dirs.toSorted(), preferHidden).slice(0, limit)
- }
+ if (!query) {
+ if (kind === "file") return cache.files.slice(0, limit)
+ return sortHiddenLast(cache.dirs.toSorted(), preferHidden).slice(0, limit)
+ }
- const items =
- kind === "file" ? result.files : kind === "directory" ? result.dirs : [...result.files, ...result.dirs]
+ const items =
+ kind === "file" ? cache.files : kind === "directory" ? cache.dirs : [...cache.files, ...cache.dirs]
- const searchLimit = kind === "directory" && !preferHidden ? limit * 20 : limit
- const sorted = fuzzysort.go(query, items, { limit: searchLimit }).map((item) => item.target)
- const output = kind === "directory" ? sortHiddenLast(sorted, preferHidden).slice(0, limit) : sorted
+ const searchLimit = kind === "directory" && !preferHidden ? limit * 20 : limit
+ const sorted = fuzzysort.go(query, items, { limit: searchLimit }).map((item) => item.target)
+ const output = kind === "directory" ? sortHiddenLast(sorted, preferHidden).slice(0, limit) : sorted
- log.info("search", { query, kind, results: output.length })
- return output
- })
+ log.info("search", { query, kind, results: output.length })
+ return output
})
log.info("init")
diff --git a/packages/opencode/src/provider/auth.ts b/packages/opencode/src/provider/auth.ts
index fbfab6c3b..38ef4b11f 100644
--- a/packages/opencode/src/provider/auth.ts
+++ b/packages/opencode/src/provider/auth.ts
@@ -111,26 +111,25 @@ export namespace ProviderAuth {
export class Service extends ServiceMap.Service<Service, Interface>()("@opencode/ProviderAuth") {}
- export const layer = Layer.effect(
+ export const layer: Layer.Layer<Service, never, Auth.Service | Plugin.Service> = Layer.effect(
Service,
Effect.gen(function* () {
const auth = yield* Auth.Service
+ const plugin = yield* Plugin.Service
const state = yield* InstanceState.make<State>(
- Effect.fn("ProviderAuth.state")(() =>
- Effect.promise(async () => {
- const plugins = await Plugin.list()
- return {
- hooks: Record.fromEntries(
- Arr.filterMap(plugins, (x) =>
- x.auth?.provider !== undefined
- ? Result.succeed([ProviderID.make(x.auth.provider), x.auth] as const)
- : Result.failVoid,
- ),
+ Effect.fn("ProviderAuth.state")(function* () {
+ const plugins = yield* plugin.list()
+ return {
+ hooks: Record.fromEntries(
+ Arr.filterMap(plugins, (x) =>
+ x.auth?.provider !== undefined
+ ? Result.succeed([ProviderID.make(x.auth.provider), x.auth] as const)
+ : Result.failVoid,
),
- pending: new Map<ProviderID, AuthOAuthResult>(),
- }
- }),
- ),
+ ),
+ pending: new Map<ProviderID, AuthOAuthResult>(),
+ }
+ }),
)
const methods = Effect.fn("ProviderAuth.methods")(function* () {
@@ -230,7 +229,9 @@ export namespace ProviderAuth {
}),
)
- export const defaultLayer = layer.pipe(Layer.provide(Auth.defaultLayer))
+ export const defaultLayer = Layer.suspend(() =>
+ layer.pipe(Layer.provide(Auth.defaultLayer), Layer.provide(Plugin.defaultLayer)),
+ )
const { runPromise } = makeRuntime(Service, defaultLayer)
diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts
index 40ab69e0f..861b34a7a 100644
--- a/packages/opencode/src/provider/provider.ts
+++ b/packages/opencode/src/provider/provider.ts
@@ -961,11 +961,12 @@ export namespace Provider {
}
}
- const layer: Layer.Layer<Service, never, Config.Service | Auth.Service> = Layer.effect(
+ const layer: Layer.Layer<Service, never, Config.Service | Auth.Service | Plugin.Service> = Layer.effect(
Service,
Effect.gen(function* () {
const config = yield* Config.Service
const auth = yield* Auth.Service
+ const plugin = yield* Plugin.Service
const state = yield* InstanceState.make<State>(() =>
Effect.gen(function* () {
@@ -1128,7 +1129,7 @@ export namespace Provider {
}
}
- const plugins = yield* Effect.promise(() => Plugin.list())
+ const plugins = yield* plugin.list()
for (const plugin of plugins) {
if (!plugin.auth) continue
const providerID = ProviderID.make(plugin.auth.provider)
@@ -1541,7 +1542,9 @@ export namespace Provider {
}),
)
- export const defaultLayer = layer.pipe(Layer.provide(Config.defaultLayer), Layer.provide(Auth.defaultLayer))
+ export const defaultLayer = Layer.suspend(() =>
+ layer.pipe(Layer.provide(Config.defaultLayer), Layer.provide(Auth.defaultLayer), Layer.provide(Plugin.defaultLayer)),
+ )
const { runPromise } = makeRuntime(Service, defaultLayer)