1import { describe, expect, it } from "vite-plus/test";
2import { ProviderInstanceId, type ModelCapabilities } from "@t3tools/contracts";
3
4import {
5 applyClaudePromptEffortPrefix,
6 buildExplicitProviderOptionSelectionsFromDescriptors,
7 buildProviderOptionSelectionsFromDescriptors,
8 createModelCapabilities,
9 createModelSelection,
10 getProviderOptionCurrentLabel,
11 getProviderOptionDescriptors,
12 readCustomModelEntries,
13 modelSelectionsEqual,
14 withImplicitFastModeDefault,
15} from "./model.ts";
16
17const codexCaps: ModelCapabilities = createModelCapabilities({
18 optionDescriptors: [
19 {
20 id: "reasoningEffort",
21 label: "Reasoning",
22 type: "select",
23 options: [
24 { id: "xhigh", label: "Extra High" },
25 { id: "high", label: "High", isDefault: true },
26 ],
27 currentValue: "high",
28 },
29 {
30 id: "fastMode",
31 label: "Fast Mode",
32 type: "boolean",
33 },
34 ],
35});
36
37describe("descriptor helpers", () => {
38 it("builds dispatch options only from explicit selections", () => {
39 const descriptors = getProviderOptionDescriptors({
40 caps: codexCaps,
41 selections: [{ id: "fastMode", value: true }],
42 });
43
44 expect(buildExplicitProviderOptionSelectionsFromDescriptors(descriptors, undefined)).toBe(
45 undefined,
46 );
47 expect(
48 buildExplicitProviderOptionSelectionsFromDescriptors(descriptors, [
49 { id: "fastMode", value: true },
50 ]),
51 ).toEqual([{ id: "fastMode", value: true }]);
52 });
53
54 it("dispatches an old Ultracode effort as Extra High with the Ultracode toggle", () => {
55 const legacy = [{ id: "effort", value: "ultracode" }];
56 const descriptors = getProviderOptionDescriptors({
57 caps: createModelCapabilities({
58 optionDescriptors: [
59 {
60 id: "effort",
61 label: "Reasoning",
62 type: "select",
63 options: [
64 { id: "high", label: "High", isDefault: true },
65 { id: "xhigh", label: "Extra High" },
66 ],
67 },
68 { id: "ultracode", label: "Ultracode", type: "boolean" },
69 ],
70 }),
71 selections: legacy,
72 });
73
74 expect(buildExplicitProviderOptionSelectionsFromDescriptors(descriptors, legacy)).toEqual([
75 { id: "effort", value: "xhigh" },
76 { id: "ultracode", value: true },
77 ]);
78 });
79
80 it("compares complete model selections independent of option ordering", () => {
81 const left = createModelSelection(ProviderInstanceId.make("codex"), "gpt-5.4", [
82 { id: "reasoningEffort", value: "high" },
83 { id: "fastMode", value: true },
84 ]);
85 const reordered = createModelSelection(ProviderInstanceId.make("codex"), "gpt-5.4", [
86 { id: "fastMode", value: true },
87 { id: "reasoningEffort", value: "high" },
88 ]);
89
90 expect(modelSelectionsEqual(left, reordered)).toBe(true);
91 expect(
92 modelSelectionsEqual(left, {
93 ...reordered,
94 options: [
95 { id: "fastMode", value: true },
96 { id: "reasoningEffort", value: "medium" },
97 ],
98 }),
99 ).toBe(false);
100 expect(modelSelectionsEqual(left, { ...reordered, model: "gpt-5.5" })).toBe(false);
101 });
102});
103
104describe("applyClaudePromptEffortPrefix", () => {
105 it("keeps slash commands intact when ultrathink is selected", () => {
106 expect(applyClaudePromptEffortPrefix("/compact", "ultrathink")).toBe("/compact");
107 expect(applyClaudePromptEffortPrefix(" /compact keep recent errors ", "ultrathink")).toBe(
108 "/compact keep recent errors",
109 );
110 expect(applyClaudePromptEffortPrefix(" /review src/model.ts ", "ultrathink")).toBe(
111 "/review src/model.ts",
112 );
113 expect(applyClaudePromptEffortPrefix("/security-review", "ultrathink")).toBe(
114 "/security-review",
115 );
116 expect(applyClaudePromptEffortPrefix("/plugin:skill run", "ultrathink")).toBe(
117 "/plugin:skill run",
118 );
119 expect(applyClaudePromptEffortPrefix("/deploy.prod to staging", "ultrathink")).toBe(
120 "/deploy.prod to staging",
121 );
122 });
123
124 it("still adds the ultrathink prefix to ordinary prompts", () => {
125 expect(applyClaudePromptEffortPrefix("Investigate this failure", "ultrathink")).toBe(
126 "Ultrathink:\nInvestigate this failure",
127 );
128 expect(applyClaudePromptEffortPrefix("/home/theo/app.ts crashed on load", "ultrathink")).toBe(
129 "Ultrathink:\n/home/theo/app.ts crashed on load",
130 );
131 });
132});
133
134describe("readCustomModelEntries", () => {
135 const capabilities: ModelCapabilities = {
136 optionDescriptors: [
137 {
138 id: "effort",
139 label: "Reasoning",
140 type: "select",
141 options: [{ id: "high", label: "High", isDefault: true }],
142 currentValue: "high",
143 },
144 ],
145 };
146
147 it("resolves bare slugs and entries, trimming and deduplicating on slug", () => {
148 expect(
149 readCustomModelEntries([
150 " bare ",
151 { slug: "named", name: " Named ", capabilities },
152 "bare",
153 { slug: "named", name: "Second" },
154 "",
155 { name: "no slug" },
156 42,
157 ]),
158 ).toEqual([
159 { slug: "bare", name: "bare", capabilities: null },
160 { slug: "named", name: "Named", capabilities },
161 ]);
162 });
163
164 it("drops unparseable capabilities but keeps the entry", () => {
165 expect(
166 readCustomModelEntries([{ slug: "x", capabilities: { optionDescriptors: "nope" } }]),
167 ).toEqual([{ slug: "x", name: "x", capabilities: null }]);
168 expect(readCustomModelEntries("not a list")).toEqual([]);
169 });
170});
171
172describe("provider-reported option display", () => {
173 const selection = createModelSelection(ProviderInstanceId.make("opencode"), "ling");
174 const reported = { ...selection, options: [{ id: "variant", value: "default" }] };
175 const descriptor = {
176 id: "variant",
177 label: "Reasoning",
178 type: "select" as const,
179 options: [
180 { id: "none", label: "None" },
181 { id: "thinking", label: "Thinking" },
182 ],
183 };
184
185 it("shows explicit reports without adding a choice or a dispatch option", () => {
186 expect(getProviderOptionCurrentLabel(descriptor, selection, reported)).toBe("Default");
187 expect(
188 getProviderOptionCurrentLabel(descriptor, selection, {
189 ...reported,
190 options: [{ id: "variant", value: "thinking" }],
191 }),
192 ).toBe("Thinking");
193 expect(
194 getProviderOptionCurrentLabel(
195 { ...descriptor, currentValue: "none" },
196 { ...selection, options: [{ id: "variant", value: "none" }] },
197 reported,
198 ),
199 ).toBe("None");
200 expect(descriptor.options.map((option) => option.id)).toEqual(["none", "thinking"]);
201 expect(buildProviderOptionSelectionsFromDescriptors([descriptor])).toBeUndefined();
202 expect(getProviderOptionCurrentLabel(descriptor, selection)).toBe("Unknown");
203 const effortDescriptor = { ...descriptor, id: "effort", currentValue: "default" };
204 expect(getProviderOptionCurrentLabel(effortDescriptor, selection)).toBeUndefined();
205 expect(
206 getProviderOptionCurrentLabel(effortDescriptor, selection, {
207 ...reported,
208 model: "other",
209 options: [{ id: "effort", value: "default" }],
210 }),
211 ).toBeUndefined();
212 expect(
213 getProviderOptionCurrentLabel(effortDescriptor, selection, {
214 ...reported,
215 options: [{ id: "effort", value: "default" }],
216 }),
217 ).toBe("Default");
218 expect(
219 getProviderOptionCurrentLabel({ ...descriptor, currentValue: "thinking" }, selection),
220 ).toBe("Unknown");
221 });
222
223 it.each([
224 { ...selection, model: "other" },
225 { ...selection, instanceId: ProviderInstanceId.make("other") },
226 { ...selection, options: [{ id: "variant", value: "none" }] },
227 ])("ignores reports after changing the model, instance, or option: %j", (selected) => {
228 expect(getProviderOptionCurrentLabel(descriptor, selected, reported)).toBe("Unknown");
229 });
230});
231
232describe("withImplicitFastModeDefault", () => {
233 // regression: #2981 a remembered Fast mode choice must survive the implicit default on new chats
234 it("injects fastMode false only when the model exposes fastMode and no selection exists", () => {
235 const caps = createModelCapabilities({
236 optionDescriptors: [{ id: "fastMode", label: "Fast", type: "boolean", currentValue: true }],
237 });
238
239 expect(withImplicitFastModeDefault(caps, undefined)).toEqual([
240 { id: "fastMode", value: false },
241 ]);
242 expect(withImplicitFastModeDefault(caps, [{ id: "fastMode", value: true }])).toEqual([
243 { id: "fastMode", value: true },
244 ]);
245 });
246});