| 1 | import { describe, expect, it } from "vite-plus/test"; |
| 2 | import { ProviderDriverKind, type ModelCapabilities } from "@t3tools/contracts"; |
| 3 | |
| 4 | import { |
| 5 | DESCRIPTOR_PRESETS_BY_KIND, |
| 6 | descriptorFromPreset, |
| 7 | definitionFromDraft, |
| 8 | descriptorsFromCapabilities, |
| 9 | draftFromDefinition, |
| 10 | validateDraft, |
| 11 | type CustomModelDraft, |
| 12 | } from "./customModelEditor.logic"; |
| 13 | |
| 14 | const draft = (overrides: Partial<CustomModelDraft>): CustomModelDraft => ({ |
| 15 | slug: "my-model", |
| 16 | name: "", |
| 17 | descriptors: [], |
| 18 | ...overrides, |
| 19 | }); |
| 20 | |
| 21 | describe("customModelEditor.logic", () => { |
| 22 | it("round-trips a definition through the draft, marking the current value as default", () => { |
| 23 | const definition = definitionFromDraft( |
| 24 | draft({ |
| 25 | name: " My Model ", |
| 26 | descriptors: [ |
| 27 | { |
| 28 | key: "a", |
| 29 | type: "select", |
| 30 | id: "reasoningEffort", |
| 31 | label: "Reasoning", |
| 32 | choices: [ |
| 33 | { key: "a1", id: "low", label: "Low", isDefault: false }, |
| 34 | { key: "a2", id: "high", label: "", isDefault: true }, |
| 35 | ], |
| 36 | }, |
| 37 | { key: "b", type: "boolean", id: "fastMode", label: "Fast Mode", choices: [] }, |
| 38 | ], |
| 39 | }), |
| 40 | ); |
| 41 | |
| 42 | expect(definition).toEqual({ |
| 43 | slug: "my-model", |
| 44 | name: "My Model", |
| 45 | capabilities: { |
| 46 | optionDescriptors: [ |
| 47 | { |
| 48 | id: "reasoningEffort", |
| 49 | label: "Reasoning", |
| 50 | type: "select", |
| 51 | options: [ |
| 52 | { id: "low", label: "Low" }, |
| 53 | { id: "high", label: "high", isDefault: true }, |
| 54 | ], |
| 55 | currentValue: "high", |
| 56 | }, |
| 57 | { id: "fastMode", label: "Fast Mode", type: "boolean" }, |
| 58 | ], |
| 59 | }, |
| 60 | }); |
| 61 | |
| 62 | const reopened = draftFromDefinition(definition); |
| 63 | expect(reopened.name).toBe("My Model"); |
| 64 | expect(reopened.descriptors.map((descriptor) => descriptor.id)).toEqual([ |
| 65 | "reasoningEffort", |
| 66 | "fastMode", |
| 67 | ]); |
| 68 | expect(reopened.descriptors[0]!.choices.map((choice) => choice.isDefault)).toEqual([ |
| 69 | false, |
| 70 | true, |
| 71 | ]); |
| 72 | }); |
| 73 | |
| 74 | it("preserves the current choice when it differs from the built-in default", () => { |
| 75 | const descriptors = descriptorsFromCapabilities( |
| 76 | { |
| 77 | optionDescriptors: [ |
| 78 | { |
| 79 | id: "effort", |
| 80 | label: "Reasoning", |
| 81 | type: "select", |
| 82 | currentValue: "high", |
| 83 | options: [ |
| 84 | { id: "low", label: "Low", isDefault: true }, |
| 85 | { id: "high", label: "High" }, |
| 86 | ], |
| 87 | }, |
| 88 | ], |
| 89 | }, |
| 90 | ProviderDriverKind.make("claudeAgent"), |
| 91 | ); |
| 92 | expect(descriptors[0]!.choices.map((choice) => choice.isDefault)).toEqual([false, true]); |
| 93 | expect( |
| 94 | definitionFromDraft(draft({ descriptors })).capabilities?.optionDescriptors?.[0], |
| 95 | ).toMatchObject({ currentValue: "high" }); |
| 96 | }); |
| 97 | |
| 98 | it("drops prompt-injected choices when copying a built-in's descriptors", () => { |
| 99 | const [copied] = descriptorsFromCapabilities( |
| 100 | { |
| 101 | optionDescriptors: [ |
| 102 | { |
| 103 | id: "effort", |
| 104 | label: "Reasoning", |
| 105 | type: "select", |
| 106 | options: [ |
| 107 | { id: "high", label: "High", isDefault: true }, |
| 108 | { id: "ultrathink", label: "Ultrathink" }, |
| 109 | ], |
| 110 | promptInjectedValues: ["ultrathink"], |
| 111 | }, |
| 112 | ], |
| 113 | }, |
| 114 | ProviderDriverKind.make("claudeAgent"), |
| 115 | ); |
| 116 | expect(copied!.choices.map((choice) => choice.id)).toEqual(["high"]); |
| 117 | }); |
| 118 | |
| 119 | it.each([true, false, undefined])( |
| 120 | "preserves boolean values through copy and edit: %s", |
| 121 | (currentValue) => { |
| 122 | const capabilities: ModelCapabilities = { |
| 123 | optionDescriptors: [ |
| 124 | { |
| 125 | id: "thinking", |
| 126 | label: "Thinking", |
| 127 | type: "boolean", |
| 128 | ...(currentValue !== undefined ? { currentValue } : {}), |
| 129 | }, |
| 130 | ], |
| 131 | }; |
| 132 | const copied = definitionFromDraft( |
| 133 | draft({ |
| 134 | descriptors: descriptorsFromCapabilities(capabilities, ProviderDriverKind.make("cursor")), |
| 135 | }), |
| 136 | ); |
| 137 | expect(copied.capabilities).toEqual(capabilities); |
| 138 | const reopened = draftFromDefinition(copied); |
| 139 | expect(definitionFromDraft({ ...reopened, name: "Renamed" }).capabilities).toEqual( |
| 140 | capabilities, |
| 141 | ); |
| 142 | }, |
| 143 | ); |
| 144 | |
| 145 | it("excludes Claude context choices from presets and copies without changing other providers or authored entries", () => { |
| 146 | const capabilities: ModelCapabilities = { |
| 147 | optionDescriptors: [ |
| 148 | { |
| 149 | id: "contextWindow", |
| 150 | label: "Context", |
| 151 | type: "select", |
| 152 | options: [{ id: "1m", label: "1M", isDefault: true }], |
| 153 | }, |
| 154 | { id: "thinking", label: "Thinking", type: "boolean", currentValue: true }, |
| 155 | ], |
| 156 | }; |
| 157 | const claude = ProviderDriverKind.make("claudeAgent"); |
| 158 | const copied = definitionFromDraft( |
| 159 | draft({ descriptors: descriptorsFromCapabilities(capabilities, claude) }), |
| 160 | ); |
| 161 | expect(copied.capabilities?.optionDescriptors).toEqual([capabilities.optionDescriptors![1]]); |
| 162 | const presets = definitionFromDraft( |
| 163 | draft({ |
| 164 | descriptors: (DESCRIPTOR_PRESETS_BY_KIND[claude] ?? []).map(descriptorFromPreset), |
| 165 | }), |
| 166 | ); |
| 167 | expect( |
| 168 | presets.capabilities?.optionDescriptors?.some((option) => option.id === "contextWindow"), |
| 169 | ).toBe(false); |
| 170 | const cursorCopy = descriptorsFromCapabilities(capabilities, ProviderDriverKind.make("cursor")); |
| 171 | expect(cursorCopy.map((option) => option.id)).toEqual(["contextWindow", "thinking"]); |
| 172 | const authored = { slug: "custom", name: "Custom", capabilities }; |
| 173 | expect( |
| 174 | definitionFromDraft(draftFromDefinition(authored)).capabilities?.optionDescriptors?.[0], |
| 175 | ).toMatchObject(capabilities.optionDescriptors![0]!); |
| 176 | }); |
| 177 | |
| 178 | it("preserves choice descriptions when copying, renaming, and saving", () => { |
| 179 | const capabilities: ModelCapabilities = { |
| 180 | optionDescriptors: [ |
| 181 | { |
| 182 | id: "effort", |
| 183 | label: "Reasoning", |
| 184 | description: "Choose a reasoning level.", |
| 185 | type: "select", |
| 186 | options: [ |
| 187 | { id: "high", label: "High", isDefault: true }, |
| 188 | { id: "ultracode", label: "Ultracode", description: "Uses additional reasoning." }, |
| 189 | ], |
| 190 | }, |
| 191 | ], |
| 192 | }; |
| 193 | const copied = definitionFromDraft( |
| 194 | draft({ |
| 195 | descriptors: descriptorsFromCapabilities( |
| 196 | capabilities, |
| 197 | ProviderDriverKind.make("claudeAgent"), |
| 198 | ), |
| 199 | }), |
| 200 | ); |
| 201 | const reopened = draftFromDefinition(copied); |
| 202 | const saved = definitionFromDraft({ ...reopened, name: "Renamed" }); |
| 203 | expect(saved.capabilities?.optionDescriptors?.[0]).toMatchObject( |
| 204 | capabilities.optionDescriptors![0]!, |
| 205 | ); |
| 206 | expect(copied.capabilities?.optionDescriptors?.[0]).toMatchObject( |
| 207 | capabilities.optionDescriptors![0]!, |
| 208 | ); |
| 209 | }); |
| 210 | |
| 211 | it.each(Object.entries(DESCRIPTOR_PRESETS_BY_KIND))( |
| 212 | "offers saveable presets for %s", |
| 213 | (_driver, presets) => { |
| 214 | expect( |
| 215 | validateDraft(draft({ descriptors: (presets ?? []).map(descriptorFromPreset) })), |
| 216 | ).toBeNull(); |
| 217 | }, |
| 218 | ); |
| 219 | |
| 220 | it("collapses a blank name and no options back to a bare definition", () => { |
| 221 | expect(definitionFromDraft(draft({ name: " " }))).toEqual({ |
| 222 | slug: "my-model", |
| 223 | name: "my-model", |
| 224 | capabilities: null, |
| 225 | }); |
| 226 | expect(draftFromDefinition({ slug: "x", name: "x", capabilities: null }).name).toBe(""); |
| 227 | }); |
| 228 | |
| 229 | it("rejects duplicate ids, blank ids, and selects without choices", () => { |
| 230 | const select = (id: string, choices: Array<{ id: string }>) => ({ |
| 231 | key: id, |
| 232 | type: "select" as const, |
| 233 | id, |
| 234 | label: "Label", |
| 235 | choices: choices.map((choice) => ({ |
| 236 | key: choice.id, |
| 237 | label: "", |
| 238 | isDefault: false, |
| 239 | ...choice, |
| 240 | })), |
| 241 | }); |
| 242 | |
| 243 | expect(validateDraft(draft({ descriptors: [select("", [{ id: "a" }])] }))).toBe( |
| 244 | "Option 1 needs an id.", |
| 245 | ); |
| 246 | expect( |
| 247 | validateDraft( |
| 248 | draft({ descriptors: [select("effort", [{ id: "a" }]), select("effort", [{ id: "b" }])] }), |
| 249 | ), |
| 250 | ).toBe('Option 2: id "effort" is used twice.'); |
| 251 | expect(validateDraft(draft({ descriptors: [select("effort", [])] }))).toBe( |
| 252 | "Option 1 needs at least one choice.", |
| 253 | ); |
| 254 | expect( |
| 255 | validateDraft(draft({ descriptors: [select("effort", [{ id: "a" }, { id: "a" }])] })), |
| 256 | ).toBe('Option 1: choice "a" is used twice.'); |
| 257 | expect(validateDraft(draft({ descriptors: [select("effort", [{ id: "a" }])] }))).toBeNull(); |
| 258 | }); |
| 259 | }); |