fix(plugin): reload late SDK plugins (#35576)
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import fs from "fs/promises"
|
||||
import path from "path"
|
||||
import { expect } from "bun:test"
|
||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||
import { Deferred, Effect, Latch, Layer, Option, Schema, Stream } from "effect"
|
||||
import { Deferred, Effect, Latch, Layer, Option, Ref, Schema, Stream } from "effect"
|
||||
import { testEffect } from "../../core/test/lib/effect"
|
||||
import { tmpdir } from "../../core/test/fixture/tmpdir"
|
||||
import type { OpenCodeEvent } from "../src"
|
||||
@@ -26,6 +28,118 @@ const sessionID = (fixture: Fixture) => fixture.sdk.Session.ID.create()
|
||||
const location = (fixture: Fixture) =>
|
||||
fixture.sdk.Location.Ref.make({ directory: fixture.sdk.AbsolutePath.make(fixture.directory) })
|
||||
|
||||
it.live(
|
||||
"reloads every booted Location after SDK plugin registration",
|
||||
() =>
|
||||
withEmbedded("opencode-embedded-plugin-reload-", (fixture) =>
|
||||
Effect.gen(function* () {
|
||||
const opencode = yield* fixture.sdk.OpenCode.create()
|
||||
const booted = yield* Deferred.make<void>()
|
||||
const activated = yield* Deferred.make<boolean>()
|
||||
const bootCount = yield* Ref.make(0)
|
||||
const activationCount = yield* Ref.make(0)
|
||||
const secondDirectory = path.join(fixture.directory, "second")
|
||||
yield* Effect.promise(() => fs.mkdir(secondDirectory))
|
||||
const refs = [
|
||||
location(fixture),
|
||||
fixture.sdk.Location.Ref.make({ directory: fixture.sdk.AbsolutePath.make(secondDirectory) }),
|
||||
]
|
||||
const bootstrapID = `bootstrap-sdk-${crypto.randomUUID()}`
|
||||
const id = `late-sdk-${crypto.randomUUID()}`
|
||||
|
||||
yield* opencode.plugin({
|
||||
id: bootstrapID,
|
||||
effect: (ctx) =>
|
||||
Effect.gen(function* () {
|
||||
yield* ctx.tool
|
||||
.transform((draft) =>
|
||||
draft.add(
|
||||
"bootstrap_sdk_tool",
|
||||
fixture.sdk.Tool.make({
|
||||
description: "Marks the initial Location plugin generation",
|
||||
input: Schema.Struct({}),
|
||||
output: Schema.Void,
|
||||
execute: () => Effect.void,
|
||||
}),
|
||||
),
|
||||
)
|
||||
.pipe(Effect.orDie)
|
||||
if (yield* Ref.updateAndGet(bootCount, (count) => count + 1).pipe(Effect.map((count) => count === 2))) {
|
||||
yield* Deferred.succeed(booted, undefined)
|
||||
}
|
||||
}),
|
||||
})
|
||||
yield* Effect.all(
|
||||
refs.map((ref) => opencode.plugin.list({ location: ref })),
|
||||
{ discard: true },
|
||||
)
|
||||
yield* Deferred.await(booted).pipe(Effect.timeout("4 seconds"))
|
||||
yield* opencode.plugin({
|
||||
id,
|
||||
effect: (ctx) =>
|
||||
Effect.gen(function* () {
|
||||
yield* ctx.tool
|
||||
.transform((draft) =>
|
||||
draft.add(
|
||||
"late_sdk_tool",
|
||||
fixture.sdk.Tool.make({
|
||||
description: "Tool registered after Location boot",
|
||||
input: Schema.Struct({}),
|
||||
output: Schema.Void,
|
||||
execute: () => Effect.void,
|
||||
}),
|
||||
),
|
||||
)
|
||||
.pipe(Effect.orDie)
|
||||
if (
|
||||
yield* Ref.updateAndGet(activationCount, (count) => count + 1).pipe(Effect.map((count) => count === 2))
|
||||
) {
|
||||
yield* Deferred.succeed(activated, true)
|
||||
}
|
||||
}),
|
||||
})
|
||||
|
||||
expect(yield* Deferred.await(activated).pipe(Effect.timeout("10 seconds"))).toBe(true)
|
||||
}),
|
||||
),
|
||||
25_000,
|
||||
)
|
||||
|
||||
it.live(
|
||||
"keeps SDK plugin registration isolated between embedded hosts",
|
||||
() =>
|
||||
withEmbedded("opencode-embedded-plugin-isolation-", (fixture) =>
|
||||
Effect.gen(function* () {
|
||||
const first = yield* fixture.sdk.OpenCode.create()
|
||||
const second = yield* fixture.sdk.OpenCode.create()
|
||||
const firstReady = yield* Deferred.make<void>()
|
||||
const secondReady = yield* Deferred.make<void>()
|
||||
const activated = yield* Deferred.make<void>()
|
||||
const ref = location(fixture)
|
||||
const id = `isolated-sdk-${crypto.randomUUID()}`
|
||||
|
||||
yield* first.plugin({
|
||||
id: `first-ready-${crypto.randomUUID()}`,
|
||||
effect: () => Deferred.succeed(firstReady, undefined),
|
||||
})
|
||||
yield* second.plugin({
|
||||
id: `second-ready-${crypto.randomUUID()}`,
|
||||
effect: () => Deferred.succeed(secondReady, undefined),
|
||||
})
|
||||
yield* Effect.all([first.plugin.list({ location: ref }), second.plugin.list({ location: ref })], {
|
||||
discard: true,
|
||||
})
|
||||
yield* Effect.all([Deferred.await(firstReady), Deferred.await(secondReady)], { discard: true })
|
||||
|
||||
yield* first.plugin({ id, effect: () => Deferred.succeed(activated, undefined) })
|
||||
yield* Deferred.await(activated).pipe(Effect.timeout("5 seconds"))
|
||||
|
||||
expect((yield* second.plugin.list({ location: ref })).data.map((plugin) => String(plugin.id))).not.toContain(id)
|
||||
}),
|
||||
),
|
||||
15_000,
|
||||
)
|
||||
|
||||
it.live(
|
||||
"embedded client uses the real router and handlers",
|
||||
() =>
|
||||
@@ -42,14 +156,17 @@ it.live(
|
||||
id: `embedded-tools-${crypto.randomUUID()}`,
|
||||
effect: (ctx) =>
|
||||
ctx.tool
|
||||
.register({
|
||||
embedded_tool: fixture.sdk.Tool.make({
|
||||
description: "Embedded test tool",
|
||||
input: Schema.Struct({}),
|
||||
output: Schema.Struct({ ok: Schema.Boolean }),
|
||||
execute: () => Effect.succeed({ ok: true }),
|
||||
}),
|
||||
})
|
||||
.transform((draft) =>
|
||||
draft.add(
|
||||
"embedded_tool",
|
||||
fixture.sdk.Tool.make({
|
||||
description: "Embedded test tool",
|
||||
input: Schema.Struct({}),
|
||||
output: Schema.Struct({ ok: Schema.Boolean }),
|
||||
execute: () => Effect.succeed({ ok: true }),
|
||||
}),
|
||||
),
|
||||
)
|
||||
.pipe(Effect.orDie),
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user