import { describe, expect, it } from "bun:test"; import { type OpenCodeClient } from "@opencode-ai/client"; import { OpencodeRuntimeAdapter } from "../../src/runtime/opencode.js"; const createRuntimeAdapter = (client: unknown) => Object.assign(new OpencodeRuntimeAdapter(), { clientPromise: Promise.resolve(client as OpenCodeClient), }) as OpencodeRuntimeAdapter; const createEventStream = (events: unknown[]) => ({ async *[Symbol.asyncIterator]() { for (const event of events) { yield event; } }, }); describe("OpencodeRuntimeAdapter V2 interaction responses", () => { it("replies through the top-level V2 permission API", async () => { const runtime = createRuntimeAdapter({ permission: { reply: async (parameters: Record) => { expect(parameters).toEqual({ sessionID: "session-1", requestID: "request-1", reply: "once", message: "approved", }); }, }, }); await expect( runtime.replyPermission({ requestId: "request-1", sessionId: "session-1", reply: "once", message: "approved", }), ).resolves.toBeUndefined(); }); it("replies through the top-level V2 question API", async () => { const runtime = createRuntimeAdapter({ question: { reply: async (parameters: Record) => { expect(parameters).toEqual({ sessionID: "session-1", requestID: "request-1", answers: [["A"]], }); }, }, }); await expect( runtime.replyQuestion({ requestId: "request-1", sessionId: "session-1", answers: [["A"]], }), ).resolves.toBeUndefined(); }); it("rejects through the top-level V2 question API", async () => { const runtime = createRuntimeAdapter({ question: { reject: async (parameters: Record) => { expect(parameters).toEqual({ sessionID: "session-1", requestID: "request-1", }); }, }, }); await expect( runtime.rejectQuestion({ requestId: "request-1", sessionId: "session-1", }), ).resolves.toBeUndefined(); }); it("maps frontend question answers to the V2 Form API", async () => { const runtime = createRuntimeAdapter({ form: { get: async (parameters: Record) => { expect(parameters).toEqual({ sessionID: "session-1", formID: "frm_1" }); return { fields: [ { key: "scope", type: "string", title: "范围", options: [{ value: "urban", label: "城区" }], }, { key: "confirmed", type: "boolean", title: "确认" }, ], }; }, reply: async (parameters: Record) => { expect(parameters).toEqual({ sessionID: "session-1", formID: "frm_1", answer: { scope: "urban", confirmed: true }, }); }, }, }); await expect( runtime.replyQuestion({ requestId: "frm_1", sessionId: "session-1", answers: [["城区"], ["是"]], }), ).resolves.toBeUndefined(); }); it("cancels V2 Form requests by form ID", async () => { const runtime = createRuntimeAdapter({ form: { cancel: async (parameters: Record) => { expect(parameters).toEqual({ sessionID: "session-1", formID: "frm_1" }); }, }, }); await expect( runtime.rejectQuestion({ requestId: "frm_1", sessionId: "session-1" }), ).resolves.toBeUndefined(); }); it("normalizes V2 Form and execution events for the chat stream", async () => { const runtime = createRuntimeAdapter({ event: { subscribe: () => createEventStream([ { type: "form.created", data: { form: { id: "frm_1", sessionID: "session-1", title: "分析参数", fields: [ { key: "scope", type: "string", title: "范围", description: "选择分析范围", options: [{ value: "urban", label: "城区" }], }, ], }, }, }, { type: "session.execution.succeeded", data: { sessionID: "session-1" }, }, ]), }, }); const events = []; for await (const event of await runtime.subscribeEvents()) { events.push(event); } expect(events).toEqual([ { type: "question.asked", sessionId: "session-1", request: { id: "frm_1", questions: [ { header: "范围", question: "选择分析范围", options: [{ label: "城区", description: "" }], multiple: false, custom: false, }, ], }, }, { type: "session.execution.succeeded", sessionId: "session-1" }, ]); }); });