Browse Source

refactor(instance): remove remaining bind call sites (#27731)

Shoubhit Dash 3 months ago
parent
commit
fa9a2cb24d

+ 31 - 11
packages/opencode/src/effect/bridge.ts

@@ -1,4 +1,4 @@
-import { Effect, Exit, Fiber } from "effect"
+import { Context, Effect, Exit, Fiber } from "effect"
 import { WorkspaceContext } from "@/control-plane/workspace-context"
 import { WorkspaceContext } from "@/control-plane/workspace-context"
 import { Instance } from "@/project/instance"
 import { Instance } from "@/project/instance"
 import type { InstanceContext } from "@/project/instance-context"
 import type { InstanceContext } from "@/project/instance-context"
@@ -11,6 +11,7 @@ export interface Shape {
   readonly promise: <A, E, R>(effect: Effect.Effect<A, E, R>) => Promise<A>
   readonly promise: <A, E, R>(effect: Effect.Effect<A, E, R>) => Promise<A>
   readonly fork: <A, E, R>(effect: Effect.Effect<A, E, R>) => Fiber.Fiber<A, E>
   readonly fork: <A, E, R>(effect: Effect.Effect<A, E, R>) => Fiber.Fiber<A, E>
   readonly run: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E>
   readonly run: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E>
+  readonly bind: <Args extends readonly unknown[], Result>(fn: (...args: Args) => Result) => (...args: Args) => Result
 }
 }
 
 
 function restore<R>(instance: InstanceContext | undefined, workspace: WorkspaceID | undefined, fn: () => R): R {
 function restore<R>(instance: InstanceContext | undefined, workspace: WorkspaceID | undefined, fn: () => R): R {
@@ -22,6 +23,28 @@ function restore<R>(instance: InstanceContext | undefined, workspace: WorkspaceI
   return fn()
   return fn()
 }
 }
 
 
+function captureSync() {
+  const fiber = Fiber.getCurrent()
+  const value = fiber ? Context.getReferenceUnsafe(fiber.context, InstanceRef) : undefined
+  const instance =
+    value ??
+    (() => {
+      try {
+        return Instance.current
+      } catch (err) {
+        if (!(err instanceof LocalContext.NotFound)) throw err
+      }
+    })()
+  const workspace = (fiber ? Context.getReferenceUnsafe(fiber.context, WorkspaceRef) : undefined) ??
+    WorkspaceContext.workspaceID
+  return { instance, workspace }
+}
+
+export const bind = <Args extends readonly unknown[], Result>(fn: (...args: Args) => Result) => {
+  const captured = captureSync()
+  return (...args: Args) => restore(captured.instance, captured.workspace, () => fn(...args))
+}
+
 /**
 /**
  * Bridge from Effect into a Promise-returning JS callback while installing
  * Bridge from Effect into a Promise-returning JS callback while installing
  * legacy `Instance.context` and `WorkspaceContext` AsyncLocalStorage for
  * legacy `Instance.context` and `WorkspaceContext` AsyncLocalStorage for
@@ -45,16 +68,9 @@ export function make(): Effect.Effect<Shape> {
   return Effect.gen(function* () {
   return Effect.gen(function* () {
     const ctx = yield* Effect.context()
     const ctx = yield* Effect.context()
     const value = yield* InstanceRef
     const value = yield* InstanceRef
-    const instance =
-      value ??
-      (() => {
-        try {
-          return Instance.current
-        } catch (err) {
-          if (!(err instanceof LocalContext.NotFound)) throw err
-        }
-      })()
-    const workspace = (yield* WorkspaceRef) ?? WorkspaceContext.workspaceID
+    const captured = captureSync()
+    const instance = value ?? captured.instance
+    const workspace = (yield* WorkspaceRef) ?? captured.workspace
     const attach = <A, E, R>(effect: Effect.Effect<A, E, R>) => attachWith(effect, { instance, workspace })
     const attach = <A, E, R>(effect: Effect.Effect<A, E, R>) => attachWith(effect, { instance, workspace })
     const wrap = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
     const wrap = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
       attach(effect).pipe(Effect.provide(ctx)) as Effect.Effect<A, E, never>
       attach(effect).pipe(Effect.provide(ctx)) as Effect.Effect<A, E, never>
@@ -72,6 +88,10 @@ export function make(): Effect.Effect<Shape> {
             ),
             ),
           )
           )
         }),
         }),
+      bind:
+        <Args extends readonly unknown[], Result>(fn: (...args: Args) => Result) =>
+        (...args: Args) =>
+          restore(instance, workspace, () => fn(...args)),
     } satisfies Shape
     } satisfies Shape
   })
   })
 }
 }

+ 3 - 2
packages/opencode/src/file/watcher.ts

@@ -6,6 +6,7 @@ import { readdir, realpath } from "fs/promises"
 import path from "path"
 import path from "path"
 import { Bus } from "@/bus"
 import { Bus } from "@/bus"
 import { BusEvent } from "@/bus/bus-event"
 import { BusEvent } from "@/bus/bus-event"
+import { EffectBridge } from "@/effect/bridge"
 import { InstanceState } from "@/effect/instance-state"
 import { InstanceState } from "@/effect/instance-state"
 import { Flag } from "@opencode-ai/core/flag/flag"
 import { Flag } from "@opencode-ai/core/flag/flag"
 import { Git } from "@/git"
 import { Git } from "@/git"
@@ -88,13 +89,13 @@ export const layer = Layer.effect(
           if (!w) return
           if (!w) return
 
 
           log.info("watcher backend", { directory: ctx.directory, platform: process.platform, backend })
           log.info("watcher backend", { directory: ctx.directory, platform: process.platform, backend })
-
+          const bridge = yield* EffectBridge.make()
           const subs: ParcelWatcher.AsyncSubscription[] = []
           const subs: ParcelWatcher.AsyncSubscription[] = []
           yield* Effect.addFinalizer(() =>
           yield* Effect.addFinalizer(() =>
             Effect.promise(() => Promise.allSettled(subs.map((sub) => sub.unsubscribe()))),
             Effect.promise(() => Promise.allSettled(subs.map((sub) => sub.unsubscribe()))),
           )
           )
 
 
-          const cb: ParcelWatcher.SubscribeCallback = InstanceState.bind((err, evts) => {
+          const cb: ParcelWatcher.SubscribeCallback = bridge.bind((err, evts) => {
             if (err) return
             if (err) return
             for (const evt of evts) {
             for (const evt of evts) {
               if (evt.type === "create") void Bus.publish(Event.Updated, { file: evt.path, event: "add" })
               if (evt.type === "create") void Bus.publish(Event.Updated, { file: evt.path, event: "add" })

+ 1 - 1
packages/opencode/src/session/llm.ts

@@ -256,7 +256,7 @@ const live: Layer.Layer<
 
 
         const bridge = yield* EffectBridge.make()
         const bridge = yield* EffectBridge.make()
         const approvedToolsForSession = new Set<string>()
         const approvedToolsForSession = new Set<string>()
-        workflowModel.approvalHandler = InstanceState.bind(async (approvalTools) => {
+        workflowModel.approvalHandler = bridge.bind(async (approvalTools) => {
           const uniqueNames = [...new Set(approvalTools.map((t: { name: string }) => t.name))] as string[]
           const uniqueNames = [...new Set(approvalTools.map((t: { name: string }) => t.name))] as string[]
           // Auto-approve tools that were already approved in this session
           // Auto-approve tools that were already approved in this session
           // (prevents infinite approval loops for server-side MCP tools)
           // (prevents infinite approval loops for server-side MCP tools)

+ 3 - 3
packages/opencode/src/storage/db.ts

@@ -11,7 +11,7 @@ import path from "path"
 import { readFileSync, readdirSync, existsSync } from "fs"
 import { readFileSync, readdirSync, existsSync } from "fs"
 import { Flag } from "@opencode-ai/core/flag/flag"
 import { Flag } from "@opencode-ai/core/flag/flag"
 import { InstallationChannel } from "@opencode-ai/core/installation/version"
 import { InstallationChannel } from "@opencode-ai/core/installation/version"
-import { InstanceState } from "@/effect/instance-state"
+import { EffectBridge } from "@/effect/bridge"
 import { init } from "#db"
 import { init } from "#db"
 import { Effect, Schema } from "effect"
 import { Effect, Schema } from "effect"
 
 
@@ -167,7 +167,7 @@ export function use<T>(callback: (trx: TxOrDb) => T): T {
 }
 }
 
 
 export function effect(fn: () => any | Promise<any>) {
 export function effect(fn: () => any | Promise<any>) {
-  const bound = InstanceState.bind(fn)
+  const bound = EffectBridge.bind(fn)
   try {
   try {
     ctx.use().effects.push(bound)
     ctx.use().effects.push(bound)
   } catch {
   } catch {
@@ -188,7 +188,7 @@ export function transaction<T>(
   } catch (err) {
   } catch (err) {
     if (err instanceof LocalContext.NotFound) {
     if (err instanceof LocalContext.NotFound) {
       const effects: (() => void | Promise<void>)[] = []
       const effects: (() => void | Promise<void>)[] = []
-      const txCallback = InstanceState.bind((tx: TxOrDb) => ctx.provide({ tx, effects }, () => callback(tx)))
+      const txCallback = EffectBridge.bind((tx: TxOrDb) => ctx.provide({ tx, effects }, () => callback(tx)))
       const result = Client().transaction(txCallback, { behavior: options?.behavior })
       const result = Client().transaction(txCallback, { behavior: options?.behavior })
       for (const effect of effects) effect()
       for (const effect of effects) effect()
       return result as NotPromise<T>
       return result as NotPromise<T>