mirror of
https://github.com/anomalyco/opencode.git
synced 2026-09-04 16:24:56 +00:00
106 lines
3.7 KiB
TypeScript
106 lines
3.7 KiB
TypeScript
import { expect } from "bun:test"
|
|
import { Plugin } from "@opencode-ai/core/plugin"
|
|
import { Rpc } from "@opencode-ai/core/rpc"
|
|
import { Bus } from "@opencode-ai/core/bus"
|
|
import { Location } from "@opencode-ai/core/location"
|
|
import { PluginTestLayer } from "./fixture"
|
|
import { Effect, Exit, Schema } from "effect"
|
|
import { testEffect } from "../lib/effect"
|
|
|
|
const it = testEffect(PluginTestLayer)
|
|
const Echo = Rpc.define({
|
|
id: "shared-echo",
|
|
methods: {
|
|
echo: { input: Schema.String, output: Schema.String },
|
|
fail: {
|
|
input: Schema.String,
|
|
output: Schema.String,
|
|
errors: { missing: Schema.Struct({ attempts: Schema.FiniteFromString }) },
|
|
},
|
|
},
|
|
events: { updated: { schema: Schema.Struct({ text: Schema.String }) } },
|
|
})
|
|
|
|
it.effect("Effect plugins register, call, and publish RPCs independently of plugin identity", () =>
|
|
Effect.gen(function* () {
|
|
const plugins = yield* Plugin.Service
|
|
const rpc = yield* Rpc.Service
|
|
const bus = yield* Bus.Service
|
|
const location = yield* Location.Service
|
|
const events: string[] = []
|
|
const unsubscribe = yield* bus.listen((event) =>
|
|
Effect.sync(() => {
|
|
if (event.type !== "rpc.shared-echo.updated") return
|
|
expect(event.location).toEqual({ directory: location.directory })
|
|
if (typeof event.data === "object" && event.data && "text" in event.data && typeof event.data.text === "string")
|
|
events.push(event.data.text)
|
|
}),
|
|
)
|
|
yield* plugins.activate([
|
|
{
|
|
id: "implementer",
|
|
version: "1",
|
|
effect: (ctx) =>
|
|
Effect.gen(function* () {
|
|
const registration = yield* ctx.rpc.register(Echo, {
|
|
echo: (value) => Effect.succeed(`${value}!`),
|
|
fail: (value, context) => Effect.fail(context.error("missing", "Missing", { attempts: Number(value) })),
|
|
})
|
|
yield* registration.events.emit("updated", { text: "ready" })
|
|
}).pipe(Effect.orDie),
|
|
},
|
|
{
|
|
id: "consumer",
|
|
version: "1",
|
|
effect: (ctx) =>
|
|
Effect.gen(function* () {
|
|
expect(yield* ctx.rpc(Echo).echo("hello")).toBe("hello!")
|
|
expect(yield* ctx.rpc(Echo).fail("2").pipe(Effect.flip)).toEqual({
|
|
type: "missing",
|
|
message: "Missing",
|
|
data: { attempts: 2 },
|
|
})
|
|
}).pipe(Effect.orDie),
|
|
},
|
|
])
|
|
expect(events).toEqual(["ready"])
|
|
expect(yield* rpc.client(Echo).echo("hello")).toBe("hello!")
|
|
yield* plugins.activate([])
|
|
expect(Exit.isFailure(yield* rpc.client(Echo).echo("hello").pipe(Effect.exit))).toBe(true)
|
|
yield* unsubscribe
|
|
}),
|
|
)
|
|
|
|
it.effect("failed plugin setup removes RPC overrides and restores the previous implementation", () =>
|
|
Effect.gen(function* () {
|
|
const plugins = yield* Plugin.Service
|
|
const rpc = yield* Rpc.Service
|
|
yield* plugins.activate([
|
|
{
|
|
id: "implementer",
|
|
version: "1",
|
|
effect: (ctx) =>
|
|
ctx.rpc
|
|
.register(Echo, {
|
|
echo: () => Effect.succeed("original"),
|
|
fail: (_input, context) => Effect.fail(context.error("missing", "Missing", { attempts: 1 })),
|
|
})
|
|
.pipe(Effect.asVoid, Effect.orDie),
|
|
},
|
|
])
|
|
yield* plugins.activate([
|
|
{
|
|
id: "implementer",
|
|
version: "2",
|
|
effect: (ctx) =>
|
|
ctx.rpc
|
|
.register(Echo, {
|
|
echo: () => Effect.succeed("replacement"),
|
|
fail: (_input, context) => Effect.fail(context.error("missing", "Missing", { attempts: 1 })),
|
|
})
|
|
.pipe(Effect.andThen(Effect.die(new Error("setup failed"))), Effect.orDie),
|
|
},
|
|
])
|
|
expect(yield* rpc.client(Echo).echo("hello")).toBe("original")
|
|
}),
|
|
)
|