Skip to content
Source
Sign inSign up

opencodeAcp.test.ts

143 lines4.5 KB

1import type { AcpAdapterV2Flavor } from "@t3tools/provider-acp/server/adapter";
2import { describe, expect, it } from "vite-plus/test";
3​
4import { extractOpenCodeElicitationForm } from "./opencodeAcp.ts";
5​
6type ElicitationRequest = Parameters<NonNullable<AcpAdapterV2Flavor["extractElicitationForm"]>>[0];
7​
8const request = (properties: Record<string, unknown>): ElicitationRequest =>
9 ({
10 mode: "form",
11 sessionId: "session-id",
12 message: "Questions",
13 requestedSchema: { type: "object", properties },
14 }) as ElicitationRequest;
15​
16const colorQuestion = {
17 type: "string",
18 title: "Color preference",
19 description: "Which color do you prefer?",
20 oneOf: [
21 { const: "red", title: "Red", description: "Choose the color red" },
22 { const: "blue", title: "Blue" },
23 ],
24 default: "red",
25};
26const colorCompanion = {
27 type: "string",
28 title: "Color preference (other)",
29 description: "Type your own answer",
30 minLength: 1,
31};
32const multiColorQuestion = {
33 type: "array",
34 title: "Colors",
35 description: "Which colors?",
36 items: {
37 anyOf: [
38 { const: "red", title: "Red" },
39 { const: "blue", title: "Blue" },
40 ],
41 },
42};
43const multiColorCompanion = { type: "string", description: "Add your own answer" };
44const attachment = '\n\nAttached image "shot.png": "/tmp/shot.png"';
45​
46describe("OpenCode ACP question form", () => {
47 it("folds the custom-answer companion into its question", () => {
48 const form = extractOpenCodeElicitationForm(
49 request({
50 q0: colorQuestion,
51 q0_custom: colorCompanion,
52 q1: { type: "string", title: "Notes", description: "Anything else?" },
53 }),
54 );
55​
56 expect(form?.questions).toEqual([
57 {
58 id: "q0",
59 header: "Color preference",
60 question: "Which color do you prefer?",
61 options: [
62 { label: "Red", description: "Choose the color red", value: "red" },
63 { label: "Blue", description: "Blue", value: "blue" },
64 ],
65 allowCustomAnswer: true,
66 },
67 {
68 id: "q1",
69 header: "Notes",
70 question: "Anything else?",
71 options: [],
72 allowCustomAnswer: true,
73 },
74 ]);
75 expect(form?.respond({ q0: "blue", q1: "Later" })).toEqual({
76 action: "accept",
77 content: { q0: "blue", q1: "Later" },
78 });
79 });
80​
81 it("prefers an option value over an earlier option label", () => {
82 const form = extractOpenCodeElicitationForm(
83 request({
84 q0: {
85 ...colorQuestion,
86 oneOf: [
87 { const: "a", title: "b" },
88 { const: "b", title: "B" },
89 ],
90 },
91 q0_custom: colorCompanion,
92 }),
93 );
94​
95 expect(form?.respond({ q0: "b" })).toEqual({ action: "accept", content: { q0: "b" } });
96 });
97​
98 it("sends a free-form answer to the companion field", () => {
99 const form = extractOpenCodeElicitationForm(
100 request({ q0: colorQuestion, q0_custom: colorCompanion }),
101 );
102​
103 expect(form?.respond({ q0: "green" })).toEqual({
104 action: "accept",
105 content: { q0_custom: "green" },
106 });
107 });
108​
109 it("keeps attachment text with the selected option", () => {
110 const form = extractOpenCodeElicitationForm(
111 request({ q0: colorQuestion, q0_custom: colorCompanion }),
112 );
113​
114 expect(form?.respond({ q0: `blue${attachment}` })).toEqual({
115 action: "accept",
116 content: { q0: "blue", q0_custom: `Blue${attachment}` },
117 });
118 });
119​
120 it("cancels empty or multi-value answers to a single-select question", () => {
121 const form = extractOpenCodeElicitationForm(
122 request({ q0: colorQuestion, q0_custom: colorCompanion }),
123 );
124​
125 expect(form?.respond({})).toEqual({ action: "cancel" });
126 expect(form?.respond({ q0: " " })).toEqual({ action: "cancel" });
127 expect(form?.respond({ q0: ["red", "blue"] })).toEqual({ action: "cancel" });
128 });
129​
130 it.each([
131 ["a text-only form", { q0: { type: "string", title: "Name", description: "Who?" } }],
132 ["an unrelated field", { q0: colorQuestion, q0_custom: colorCompanion, extra: {} }],
133 ["a multi-select choice", { q0: multiColorQuestion, q0_custom: multiColorCompanion }],
134 ["an orphan companion", { q0_custom: colorCompanion }],
135 ["an enum choice", { q0: colorQuestion, q1: { type: "string", title: "Size", enum: ["S"] } }],
136 [
137 "a choice without a title",
138 { q0: colorQuestion, q1: { ...colorQuestion, oneOf: [{ const: "red" }] } },
139 ],
140 ])("leaves %s to the generic mapping", (_, properties) => {
141 expect(extractOpenCodeElicitationForm(request(properties))).toBeUndefined();
142 });
143});