import { OpenCode, type OpenCodeEvent, type SessionMessageInfo } from "@opencode-ai/client/promise" type DurableEvent = Extract type EphemeralEvent = Exclude type RequestRecord = { readonly method: string readonly path: string readonly body?: unknown } type FixtureOptions = { readonly onPrompt?: (input: { readonly sessionID: string readonly id: string readonly body: unknown readonly signal: AbortSignal readonly send: (event: unknown) => void }) => void | Promise readonly onInterrupt?: (input: { readonly sessionID: string readonly send: (event: unknown) => void }) => void | Promise readonly onPermissionReply?: (input: { readonly sessionID: string readonly requestID: string readonly reply: string readonly body: unknown readonly send: (event: unknown) => void }) => void | Promise readonly onFormCancel?: (input: { readonly sessionID: string readonly formID: string readonly send: (event: unknown) => void }) => void | Promise } const ids = { next: 0 } export function durableEvent( type: Type, data: Extract["data"], ) { ids.next++ return { id: `evt_${ids.next}`, created: ids.next, type, durable: { aggregateID: "test", seq: ids.next, version: 1 }, data, } } export function ephemeralEvent( type: Type, data: Extract["data"], ) { ids.next++ return { id: `evt_${ids.next}`, created: ids.next, type, data } } export function createSseFixture(options: FixtureOptions = {}) { const encoder = new TextEncoder() const streams = new Set>() const requests: RequestRecord[] = [] const messages = new Map() const send = (event: unknown) => { for (const stream of streams) { try { stream.enqueue(encoder.encode(`data: ${JSON.stringify(event)}\n\n`)) } catch { streams.delete(stream) } } } const server = Bun.serve({ port: 0, async fetch(request) { const url = new URL(request.url) const body = request.method === "GET" ? undefined : await request.json().catch(() => undefined) requests.push({ method: request.method, path: url.pathname, ...(body === undefined ? {} : { body }) }) if (url.pathname === "/api/event") { const state: { stream?: ReadableStreamDefaultController } = {} return new Response( new ReadableStream({ start(stream) { state.stream = stream streams.add(stream) stream.enqueue( encoder.encode( `data: ${JSON.stringify({ id: "evt_connected", type: "server.connected", data: {} })}\n\n`, ), ) }, cancel() { if (state.stream) streams.delete(state.stream) }, }), { headers: { "content-type": "text/event-stream" } }, ) } const prompt = /^\/api\/session\/([^/]+)\/prompt$/.exec(url.pathname) if (prompt?.[1]) { const id = stringField(body, "id") if (!id) return new Response(null, { status: 400 }) await options.onPrompt?.({ sessionID: decodeURIComponent(prompt[1]), id, body, signal: request.signal, send, }) return Response.json({ data: { text: stringField(body, "text") ?? "" } }) } const message = /^\/api\/session\/([^/]+)\/message\/([^/]+)$/.exec(url.pathname) if (message?.[1] && message[2]) { const sessionID = decodeURIComponent(message[1]) const messageID = decodeURIComponent(message[2]) return Response.json({ data: messages.get(`${sessionID}/${messageID}`) ?? messages.get(messageID) ?? assistantMessage(messageID), }) } const permission = /^\/api\/session\/([^/]+)\/permission\/([^/]+)\/reply$/.exec(url.pathname) if (permission?.[1] && permission[2]) { const reply = stringField(body, "reply") if (!reply) return new Response(null, { status: 400 }) await options.onPermissionReply?.({ sessionID: decodeURIComponent(permission[1]), requestID: decodeURIComponent(permission[2]), reply, body, send, }) return new Response(null, { status: 204 }) } const form = /^\/api\/session\/([^/]+)\/form\/([^/]+)\/cancel$/.exec(url.pathname) if (form?.[1] && form[2]) { await options.onFormCancel?.({ sessionID: decodeURIComponent(form[1]), formID: decodeURIComponent(form[2]), send, }) return new Response(null, { status: 204 }) } const interrupt = /^\/api\/session\/([^/]+)\/interrupt$/.exec(url.pathname) if (interrupt?.[1]) { await options.onInterrupt?.({ sessionID: decodeURIComponent(interrupt[1]), send }) return new Response(null, { status: 204 }) } return new Response(null, { status: 404 }) }, }) return { client: OpenCode.make({ baseUrl: server.url.toString() }), messages, requests, send, async stop() { for (const stream of streams) { try { stream.close() } catch {} } streams.clear() await server.stop(true) }, } } export async function withTimeout(promise: Promise, message: string, milliseconds = 2_000) { const timeout = Promise.withResolvers() const timer = setTimeout(() => timeout.reject(new Error(message)), milliseconds) try { return await Promise.race([promise, timeout.promise]) } finally { clearTimeout(timer) } } function stringField(value: unknown, key: string) { if (!value || typeof value !== "object") return undefined const field = Reflect.get(value, key) return typeof field === "string" ? field : undefined } function assistantMessage(id: string) { return { id, type: "assistant", agent: "build", model: { providerID: "test", id: "test-model" }, content: [], finish: "stop", tokens: { input: 1, output: 1, reasoning: 0, cache: { read: 0, write: 0 } }, time: { created: 1, completed: 2 }, } satisfies SessionMessageInfo }