diff --git a/packages/core/src/effect/app-node-builder.ts b/packages/core/src/effect/app-node-builder.ts
index 5f01469dab0..d41410dbb14 100644
--- a/packages/core/src/effect/app-node-builder.ts
+++ b/packages/core/src/effect/app-node-builder.ts
@@ -1,16 +1,21 @@
import { buildLocationServiceMap } from "../location-services"
import { LocationServiceMap } from "../location-service-map"
+import { PluginRuntime } from "../plugin/runtime"
import { LayerNode } from "./layer-node"
import { makeGlobalNode } from "./app-node"
export function build(root: LayerNode.Node, replacements: LayerNode.Replacements = []) {
- let allReplacements = replacements
+ const bridge = PluginRuntime.makeBridge()
+ let allReplacements = replacements.concat([
+ [PluginRuntime.node, PluginRuntime.nodeWithBridge(bridge)],
+ [PluginRuntime.providerNode, PluginRuntime.providerNodeWithBridge(bridge)],
+ ])
// Only build the location service map if it's actually needed
- if (LayerNode.hasUnbound(root, LocationServiceMap.node) && !hasReplacement(replacements, LocationServiceMap.node)) {
- const locationMap = buildLocationServiceMap(replacements)
+ if (LayerNode.hasUnbound(root, LocationServiceMap.node) && !hasReplacement(allReplacements, LocationServiceMap.node)) {
+ const locationMap = buildLocationServiceMap(allReplacements)
const locationMapNode = makeGlobalNode({ service: LocationServiceMap.Service, layer: locationMap, deps: [] })
- allReplacements = replacements.concat([[LocationServiceMap.node, locationMapNode]])
+ allReplacements = allReplacements.concat([[LocationServiceMap.node, locationMapNode]])
}
return LayerNode.compile(root, allReplacements)
diff --git a/packages/core/src/plugin/runtime.ts b/packages/core/src/plugin/runtime.ts
index b3e1d93742d..d347a9233bc 100644
--- a/packages/core/src/plugin/runtime.ts
+++ b/packages/core/src/plugin/runtime.ts
@@ -1,6 +1,6 @@
export * as PluginRuntime from "./runtime"
-import { Context, Effect, Layer } from "effect"
+import { Context, Deferred, Effect, Layer } from "effect"
import { AgentV2 } from "../agent"
import { makeGlobalNode } from "../effect/app-node"
import { Job } from "../job"
@@ -25,51 +25,44 @@ export interface Interface {
export class Service extends Context.Service()("@opencode/PluginRuntime") {}
-export interface Cell {
- runtime?: Interface
+export interface Bridge {
+ runtime: Deferred.Deferred
}
-export const makeCell = (): Cell => ({})
+export const makeBridge = (): Bridge => ({ runtime: Deferred.makeUnsafe() })
-const unavailable = () => Effect.die("Plugin runtime is unavailable") as Effect.Effect
-const require = (cell: Cell, f: (runtime: Interface) => Effect.Effect) =>
- Effect.suspend(() => {
- const runtime = cell.runtime
- if (runtime === undefined) return unavailable()
- return f(runtime)
- })
+const require = (bridge: Bridge, f: (runtime: Interface) => Effect.Effect) =>
+ Effect.suspend(() => Deferred.await(bridge.runtime).pipe(Effect.flatMap(f)))
-const defaultCell = makeCell()
-
-export const layerWithCell = (cell: Cell) =>
+export const layerWithBridge = (bridge: Bridge) =>
Layer.succeed(
Service,
Service.of({
session: {
- get: (sessionID) => require(cell, (runtime) => runtime.session.get(sessionID)),
- create: (input) => require(cell, (runtime) => runtime.session.create(input)),
- messages: (input) => require(cell, (runtime) => runtime.session.messages(input)),
- prompt: (input) => require(cell, (runtime) => runtime.session.prompt(input)),
- resume: (sessionID) => require(cell, (runtime) => runtime.session.resume(sessionID)),
- interrupt: (sessionID) => require(cell, (runtime) => runtime.session.interrupt(sessionID)),
- synthetic: (input) => require(cell, (runtime) => runtime.session.synthetic(input)),
+ get: (sessionID) => require(bridge, (runtime) => runtime.session.get(sessionID)),
+ create: (input) => require(bridge, (runtime) => runtime.session.create(input)),
+ messages: (input) => require(bridge, (runtime) => runtime.session.messages(input)),
+ prompt: (input) => require(bridge, (runtime) => runtime.session.prompt(input)),
+ resume: (sessionID) => require(bridge, (runtime) => runtime.session.resume(sessionID)),
+ interrupt: (sessionID) => require(bridge, (runtime) => runtime.session.interrupt(sessionID)),
+ synthetic: (input) => require(bridge, (runtime) => runtime.session.synthetic(input)),
},
job: {
- start: (input) => require(cell, (runtime) => runtime.job.start(input)),
- wait: (input) => require(cell, (runtime) => runtime.job.wait(input)),
- block: (input) => require(cell, (runtime) => runtime.job.block(input)),
- background: (id) => require(cell, (runtime) => runtime.job.background(id)),
- cancel: (id) => require(cell, (runtime) => runtime.job.cancel(id)),
+ start: (input) => require(bridge, (runtime) => runtime.job.start(input)),
+ wait: (input) => require(bridge, (runtime) => runtime.job.wait(input)),
+ block: (input) => require(bridge, (runtime) => runtime.job.block(input)),
+ background: (id) => require(bridge, (runtime) => runtime.job.background(id)),
+ cancel: (id) => require(bridge, (runtime) => runtime.job.cancel(id)),
},
location: {
agent: {
- list: (ref) => require(cell, (runtime) => runtime.location.agent.list(ref)),
+ list: (ref) => require(bridge, (runtime) => runtime.location.agent.list(ref)),
},
},
}),
)
-export const providerLayerWithCell = (cell: Cell) =>
+export const providerLayerWithBridge = (bridge: Bridge) =>
Layer.effectDiscard(
Effect.gen(function* () {
const sessions = yield* SessionV2.Service
@@ -96,22 +89,34 @@ export const providerLayerWithCell = (cell: Cell) =>
},
},
}
- cell.runtime = runtime
+ yield* Deferred.succeed(bridge.runtime, runtime)
yield* Effect.addFinalizer(() =>
Effect.sync(() => {
- if (cell.runtime === runtime) cell.runtime = undefined
+ bridge.runtime = Deferred.makeUnsafe()
}),
)
}),
)
-export const layer = layerWithCell(defaultCell)
-export const providerLayer = providerLayerWithCell(defaultCell)
+const unsafeBridge = makeBridge()
+
+export const layer = layerWithBridge(unsafeBridge)
+export const providerLayer = providerLayerWithBridge(unsafeBridge)
export const node = makeGlobalNode({ service: Service, layer, deps: [] })
+export const nodeWithBridge = (bridge: Bridge) =>
+ makeGlobalNode({ service: Service, layer: layerWithBridge(bridge), deps: [] })
+
export const providerNode = makeGlobalNode({
name: "plugin-runtime-provider",
layer: providerLayer,
deps: [node, SessionV2.node, Job.node, LocationServiceMap.node],
})
+
+export const providerNodeWithBridge = (bridge: Bridge) =>
+ makeGlobalNode({
+ name: "plugin-runtime-provider",
+ layer: providerLayerWithBridge(bridge),
+ deps: [node, SessionV2.node, Job.node, LocationServiceMap.node],
+ })
diff --git a/packages/server/src/routes.ts b/packages/server/src/routes.ts
index 38ecac06bda..6a11541311c 100644
--- a/packages/server/src/routes.ts
+++ b/packages/server/src/routes.ts
@@ -50,20 +50,17 @@ export function createRoutes(password?: string) {
}
export function createEmbeddedRoutes(sdkPlugins?: SdkPlugins.Store) {
- return makeRoutes(ServerAuth.Config.layer({ username: "opencode", password: Option.none() }), sdkPlugins)
+ return makeRoutes(ServerAuth.Config.configLayer({ username: "opencode", password: Option.none() }), sdkPlugins)
}
function makeRoutes(
auth: Layer.Layer,
sdkPlugins?: SdkPlugins.Store,
) {
- const pluginRuntimeCell = PluginRuntime.makeCell()
const serviceLayer = AppNodeBuilder.build(
applicationServices,
[
[SessionExecution.node, SessionExecutionLocal.node],
- [PluginRuntime.node, PluginRuntime.layerWithCell(pluginRuntimeCell)],
- [PluginRuntime.providerNode, PluginRuntime.providerLayerWithCell(pluginRuntimeCell)],
...(sdkPlugins ? [[SdkPlugins.node, SdkPlugins.layerWithStore(sdkPlugins)] as const] : []),
],
)