refactor(simulation): scope Drive lifecycle with Effect (#36908)

This commit is contained in:
Kit Langton 2026-07-14 17:16:50 -04:00 committed by GitHub
commit 947566f611
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
30 changed files with 1313 additions and 600 deletions

View file

@ -1,31 +1,19 @@
import { Effect } from "effect"
import { SimulationControlServer } from "../control-server"
import { SimulationProtocol } from "../protocol"
import { SimulationActions, type Harness } from "./actions"
import { SimulationRenderer } from "./renderer"
export interface Server {
readonly url: string
readonly stop: () => void
}
function parseRequest(input: string | Buffer) {
return SimulationProtocol.Frontend.decodeRequest(JSON.parse(typeof input === "string" ? input : input.toString()))
}
async function handle(
harness: Harness,
request: SimulationProtocol.Frontend.Request,
finishRecording?: () => Promise<string>,
) {
function handle(harness: Harness, request: SimulationProtocol.Frontend.Request) {
switch (request.method) {
case "ui.screenshot":
return SimulationActions.screenshot(harness, request.params?.name)
case "ui.state": {
return SimulationActions.state(harness)
}
case "ui.state":
return Effect.sync(() => SimulationActions.state(harness))
case "ui.matches":
return SimulationActions.matches(harness, request.params.text)
return Effect.sync(() => SimulationActions.matches(harness, request.params.text))
case "ui.recording.finish":
if (!finishRecording) throw new Error("UI recording is not available")
return finishRecording()
return SimulationRenderer.finish(harness.renderer)
case "ui.type":
return SimulationActions.execute(harness, { type: "ui.type", text: request.params.text })
case "ui.enter":
@ -48,39 +36,22 @@ async function handle(
y: request.params.y,
})
case "ui.resize":
return SimulationActions.execute(harness, { type: "ui.resize", cols: request.params.cols, rows: request.params.rows })
return SimulationActions.execute(harness, {
type: "ui.resize",
cols: request.params.cols,
rows: request.params.rows,
})
}
}
export function start(harness: Harness, endpoint: string, finishRecording?: () => Promise<string>): Server {
const url = new URL(endpoint)
const server = Bun.serve<{ readonly drive: true }>({
hostname: url.hostname,
port: Number(url.port),
fetch(request, server) {
if (server.upgrade(request, { data: { drive: true } })) return undefined
return new Response("opencode drive ui websocket", { status: 426 })
},
websocket: {
async message(socket, message) {
let request: SimulationProtocol.Frontend.Request | undefined
try {
request = parseRequest(message)
const result = await handle(harness, request, finishRecording)
const next = SimulationProtocol.JsonRpc.success(request.id, result)
if (next) socket.send(JSON.stringify(next))
} catch (error) {
socket.send(JSON.stringify(SimulationProtocol.JsonRpc.failure(request?.id, error)))
}
},
},
export const start = Effect.fn("SimulationServer.start")(function* (harness: Harness, endpoint: string) {
return yield* SimulationControlServer.start({
endpoint,
label: "opencode drive ui websocket",
data: () => ({ drive: true as const }),
decode: SimulationProtocol.Frontend.decodeRequestEffect,
handle: (_socket, request) => handle(harness, request),
})
return {
url: endpoint,
stop: () => {
server.stop(true)
},
}
}
})
export * as SimulationServer from "./server"