Skip to content
Source
Sign inSign up

modelOptions.test.ts

858 lines30.2 KB

1import { describe, expect, it } from "vite-plus/test";
2​
3import {
4 DEFAULT_SERVER_SETTINGS,
5 ProjectId,
6 ProviderDriverKind,
7 ProviderInstanceId,
8 type ModelSelection,
9 type ServerConfig,
10 type ServerSettings,
11} from "@t3tools/contracts";
12​
13import {
14 buildModelOptions,
15 describeUnavailableModelSelection,
16 groupByProvider,
17 isModelSelectionUnavailable,
18 resolveDefaultableModelSelection,
19 resolveNewTaskModelSelection,
20 resolveSelectableModelSelection,
21 type ModelOption,
22} from "./modelOptions";
23​
24describe("mobile model options", () => {
25 it("distinguishes same-name OpenCode models without changing their routing", () => {
26 const sources = [
27 { id: "anthropic", label: "Anthropic" },
28 { id: "github-copilot", label: "GitHub Copilot" },
29 { id: "opencode", label: "OpenCode Zen" },
30 ];
31 const config = {
32 providers: [
33 {
34 instanceId: "opencode_work",
35 driver: "opencode",
36 displayName: "OpenCode Work",
37 enabled: true,
38 installed: true,
39 auth: { status: "authenticated" },
40 models: sources.map((source) => ({
41 slug: `${source.id}/claude-fable-5`,
42 name: "Claude Fable 5",
43 subProvider: source.label,
44 isCustom: false,
45 capabilities: null,
46 })),
47 },
48 ],
49 } as unknown as ServerConfig;
50 const selection = {
51 instanceId: ProviderInstanceId.make("opencode_work"),
52 model: "github-copilot/claude-fable-5",
53 };
54​
55 const options = buildModelOptions(config, selection, undefined, null);
56​
57 expect(options).toMatchObject(
58 sources.map((source) => ({
59 key: `opencode_work:${source.id}/claude-fable-5`,
60 label: "Claude Fable 5",
61 subtitle: source.label,
62 providerLabel: "OpenCode Work",
63 selection: {
64 instanceId: "opencode_work",
65 model: `${source.id}/claude-fable-5`,
66 },
67 })),
68 );
69 expect(groupByProvider(options)).toEqual([
70 { providerKey: "opencode_work", providerLabel: "OpenCode Work", models: options },
71 ]);
72 });
73​
74 it("does not materialize catalog defaults for missing stored options", () => {
75 const config = {
76 providers: [
77 {
78 instanceId: "codex",
79 driver: "codex",
80 displayName: "Codex",
81 enabled: true,
82 installed: true,
83 auth: { status: "authenticated" },
84 models: [
85 {
86 slug: "gpt-test",
87 name: "GPT Test",
88 isCustom: false,
89 capabilities: {
90 optionDescriptors: [
91 {
92 id: "serviceTier",
93 label: "Service Tier",
94 type: "select",
95 options: [
96 { id: "default", label: "Standard", isDefault: true },
97 { id: "priority", label: "Fast" },
98 ],
99 currentValue: "default",
100 },
101 ],
102 },
103 },
104 ],
105 },
106 ],
107 } as unknown as ServerConfig;
108​
109 const [option] = buildModelOptions(
110 config,
111 {
112 instanceId: ProviderInstanceId.make("codex"),
113 model: "gpt-test",
114 },
115 undefined,
116 null,
117 );
118​
119 expect(option?.capabilities?.optionDescriptors?.[0]?.id).toBe("serviceTier");
120 expect(option?.selection.options).toBeUndefined();
121​
122 const [emptyOption] = buildModelOptions(
123 config,
124 {
125 instanceId: ProviderInstanceId.make("codex"),
126 model: "gpt-test",
127 options: [],
128 },
129 undefined,
130 null,
131 );
132 expect(emptyOption?.selection).toEqual(option?.selection);
133​
134 const [explicitOption] = buildModelOptions(
135 config,
136 {
137 instanceId: ProviderInstanceId.make("codex"),
138 model: "gpt-test",
139 options: [{ id: "serviceTier", value: "priority" }],
140 },
141 undefined,
142 null,
143 );
144 expect(explicitOption?.selection.options).toEqual([{ id: "serviceTier", value: "priority" }]);
145 });
146​
147 it("limits existing threads to their provider while new tasks keep every provider", () => {
148 const providers = ["codex", "claudeAgent"].map((instanceId) => ({
149 instanceId,
150 driver: instanceId,
151 enabled: true,
152 installed: true,
153 auth: { status: "authenticated" },
154 models: [{ slug: "test", name: instanceId, capabilities: null }],
155 }));
156 const config = { providers } as unknown as ServerConfig;
157 const selection = { instanceId: ProviderInstanceId.make("codex"), model: "test" };
158​
159 expect(
160 buildModelOptions(config, selection, undefined, null).map((option) => option.providerKey),
161 ).toEqual(["codex", "claudeAgent"]);
162 expect(buildModelOptions(config, selection, selection.instanceId, null)).toEqual(
163 buildModelOptions(config, selection, undefined, null).filter(
164 (option) => option.providerKey === "codex",
165 ),
166 );
167 });
168​
169 it("keeps accounts in the continuation group and excludes other providers", () => {
170 const config = {
171 providers: [
172 { instanceId: "codex", driver: "codex", continuation: { groupKey: "shared" } },
173 { instanceId: "codex_work", driver: "codex", continuation: { groupKey: "shared" } },
174 { instanceId: "codex_other", driver: "codex", continuation: { groupKey: "other" } },
175 { instanceId: "claude", driver: "claudeAgent", continuation: { groupKey: "shared" } },
176 ].map((provider) => ({
177 ...provider,
178 enabled: true,
179 installed: true,
180 auth: { status: "authenticated" },
181 models: [{ slug: "test", name: "Test", capabilities: null }],
182 })),
183 } as unknown as ServerConfig;
184 expect(
185 buildModelOptions(config, null, ProviderInstanceId.make("codex"), null).map(
186 (option) => option.providerKey,
187 ),
188 ).toEqual(["codex", "codex_work"]);
189 });
190​
191 it("matches the web driver fallback without moving unknown Antigravity history", () => {
192 const config = {
193 providers: [
194 { instanceId: "codex", driver: "codex" },
195 { instanceId: "codex_work", driver: "codex" },
196 { instanceId: "google", driver: "antigravity" },
197 { instanceId: "google_work", driver: "antigravity" },
198 ].map((provider) => ({
199 ...provider,
200 enabled: true,
201 installed: true,
202 auth: { status: "authenticated" },
203 models: [{ slug: "test", name: "Test", capabilities: null }],
204 })),
205 } as unknown as ServerConfig;
206 expect(
207 buildModelOptions(config, null, ProviderInstanceId.make("codex"), null).map(
208 (option) => option.providerKey,
209 ),
210 ).toEqual(["codex", "codex_work"]);
211 expect(
212 buildModelOptions(config, null, ProviderInstanceId.make("google"), null).map(
213 (option) => option.providerKey,
214 ),
215 ).toEqual(["google"]);
216 });
217​
218 it("carries account badges into catalog and saved fallback models", () => {
219 const config = {
220 providers: [
221 { instanceId: "codex", driver: "codex", displayName: "Personal" },
222 { instanceId: "codex_work", driver: "codex", displayName: "Work", accentColor: "#123456" },
223 ].map((provider) => ({
224 ...provider,
225 enabled: true,
226 installed: true,
227 auth: { status: "authenticated" },
228 models: [{ slug: "test", name: "Test", capabilities: null }],
229 })),
230 } as unknown as ServerConfig;
231 const selection = { instanceId: ProviderInstanceId.make("codex_work"), model: "saved" };
232 const options = buildModelOptions(config, selection, undefined, null);
233 expect(options[0]?.providerBadge).toEqual({ displayName: "Personal", showBadge: true });
234 expect(options[1]?.providerBadge).toEqual({
235 displayName: "Work",
236 accentColor: "#123456",
237 showBadge: true,
238 });
239 expect(options[2]?.providerBadge).toEqual(options[1]?.providerBadge);
240 expect(
241 buildModelOptions(
242 { ...config, providers: config.providers.slice(0, 1) },
243 null,
244 undefined,
245 null,
246 )[0]?.providerBadge?.showBadge,
247 ).toBe(false);
248 });
249​
250 it("only badges ACP accounts that share the same agent", () => {
251 const config = {
252 settings: {
253 ...DEFAULT_SERVER_SETTINGS,
254 providerInstances: {
255 acp_one: { driver: "acpRegistry", enabled: true, config: { agentId: "one" } },
256 acp_two: { driver: "acpRegistry", enabled: true, config: { agentId: "two" } },
257 },
258 },
259 providers: ["acp_one", "acp_two"].map((instanceId) => ({
260 instanceId,
261 driver: "acpRegistry",
262 enabled: true,
263 installed: true,
264 auth: { status: "authenticated" },
265 models: [{ slug: "test", name: "Test", capabilities: null }],
266 })),
267 } as unknown as ServerConfig;
268 expect(
269 buildModelOptions(config, null, undefined, null).map(
270 (option) => option.providerBadge?.showBadge,
271 ),
272 ).toEqual([false, false]);
273 const shared = {
274 ...config,
275 settings: {
276 ...config.settings,
277 providerInstances: {
278 ...config.settings.providerInstances,
279 acp_two: config.settings.providerInstances[ProviderInstanceId.make("acp_one")]!,
280 },
281 },
282 };
283 expect(
284 buildModelOptions(shared, null, undefined, null).map(
285 (option) => option.providerBadge?.showBadge,
286 ),
287 ).toEqual([true, true]);
288 });
289​
290 it.each(["disabled", "unavailable", "missing"] as const)(
291 "retains the selected %s provider's fallback in a filtered catalog",
292 (state) => {
293 const selection = {
294 instanceId: ProviderInstanceId.make("google_work"),
295 model: "saved-model",
296 options: [{ id: "native-option", value: "saved-choice" }],
297 };
298 const provider = {
299 instanceId: selection.instanceId,
300 driver: "antigravity",
301 displayName: "Google Work",
302 enabled: state !== "disabled",
303 installed: true,
304 availability: state === "unavailable" ? "unavailable" : "available",
305 auth: { status: "authenticated" },
306 models: [{ slug: selection.model, name: "Saved model", capabilities: null }],
307 };
308 const config = {
309 providers: state === "missing" ? [] : [provider],
310 settings: { providerInstances: { google_work: { driver: "antigravity" } } },
311 } as unknown as ServerConfig;
312 const options = buildModelOptions(config, selection, selection.instanceId, null);
313 expect(options).toEqual(buildModelOptions(config, selection, undefined, null));
314 expect(options).toHaveLength(1);
315 expect(options[0]).toMatchObject({ selection, isUnavailable: true });
316 },
317 );
318​
319 it("rejects stored selections whose provider is not usable", () => {
320 const config = {
321 providers: [
322 {
323 instanceId: "codex",
324 driver: "codex",
325 enabled: true,
326 installed: true,
327 auth: { status: "authenticated" },
328 models: [],
329 },
330 {
331 instanceId: "claudeAgent",
332 driver: "claudeAgent",
333 enabled: false,
334 installed: true,
335 auth: { status: "authenticated" },
336 models: [],
337 },
338 ],
339 } as unknown as ServerConfig;
340​
341 const usable = {
342 instanceId: ProviderInstanceId.make("codex"),
343 model: "gpt-5.6-sol",
344 };
345 const disabled = {
346 instanceId: ProviderInstanceId.make("claudeAgent"),
347 model: "claude-sonnet-5",
348 };
349 const removed = {
350 instanceId: ProviderInstanceId.make("codex_personal"),
351 model: "gpt-5.6-sol",
352 };
353​
354 expect(resolveSelectableModelSelection(config, usable, null)).toBe(usable);
355 expect(resolveSelectableModelSelection(config, disabled, null)).toBeNull();
356 expect(resolveSelectableModelSelection(config, removed, null)).toBeNull();
357 expect(isModelSelectionUnavailable(config, disabled, null)).toBe(true);
358 expect(isModelSelectionUnavailable(config, removed, null)).toBe(false);
359 expect(resolveSelectableModelSelection(null, disabled, null)).toBe(disabled);
360 });
361​
362 describe("Antigravity selections", () => {
363 const selection = {
364 instanceId: ProviderInstanceId.make("google_work"),
365 model: "gemini-3.1-pro-high",
366 options: [{ id: "native-option", value: "saved/opaque-choice" }],
367 };
368 const model = {
369 slug: selection.model,
370 name: "Gemini 3.1 Pro High",
371 subProvider: "Google",
372 isCustom: false,
373 isDefault: true,
374 isLegacy: true,
375 capabilities: {
376 optionDescriptors: [
377 {
378 id: "native-option",
379 label: "Native option",
380 type: "select",
381 options: [{ id: "current/default", label: "Default", isDefault: true }],
382 currentValue: "current/default",
383 },
384 ],
385 },
386 };
387 const config = {
388 providers: [
389 {
390 instanceId: selection.instanceId,
391 driver: "antigravity",
392 displayName: "Google Work",
393 enabled: true,
394 installed: true,
395 auth: { status: "authenticated" },
396 models: [model],
397 },
398 ],
399 } as unknown as ServerConfig;
400​
401 it.each([
402 ["disabled", { enabled: false }],
403 ["signed out", { auth: { status: "unauthenticated" } }],
404 ] as const)("keeps a %s provider's selection and known model details", (_state, update) => {
405 const unavailableConfig = {
406 ...config,
407 providers: config.providers.map((provider) => ({ ...provider, ...update })),
408 };
409​
410 expect(resolveSelectableModelSelection(unavailableConfig, selection, null)).toBe(selection);
411 expect(resolveDefaultableModelSelection(unavailableConfig, selection, null)).toBe(selection);
412 expect(isModelSelectionUnavailable(unavailableConfig, selection, null)).toBe(true);
413 expect(buildModelOptions(unavailableConfig, null, undefined, null)).toEqual([]);
414 const [option] = buildModelOptions(unavailableConfig, selection, undefined, null);
415 expect(option).toMatchObject({
416 key: `google_work:${selection.model}`,
417 label: model.name,
418 subtitle: "Google",
419 providerKey: "google_work",
420 providerLabel: "Google Work",
421 providerDriver: "antigravity",
422 isDefault: false,
423 isLegacy: true,
424 isUnavailable: true,
425 capabilities: model.capabilities,
426 });
427 expect(option?.selection).toBe(selection);
428 });
429​
430 it("keeps an exact selection when its model leaves and returns to the catalog", () => {
431 const changedConfig = {
432 ...config,
433 providers: config.providers.map((provider) => ({
434 ...provider,
435 models: provider.models.map((model) => ({ ...model, slug: "gemini-3.1-pro-low" })),
436 })),
437 };
438​
439 expect(resolveDefaultableModelSelection(changedConfig, selection, null)).toBe(selection);
440 expect(isModelSelectionUnavailable(changedConfig, selection, null)).toBe(true);
441 const options = buildModelOptions(changedConfig, selection, undefined, null);
442 const missing = options.find((option) => option.selection.model === selection.model);
443 expect(missing).toMatchObject({
444 label: selection.model,
445 providerLabel: "Google Work",
446 providerDriver: "antigravity",
447 isUnavailable: true,
448 capabilities: null,
449 });
450 expect(missing?.selection).toBe(selection);
451 expect(
452 resolveNewTaskModelSelection({
453 draftSelection: null,
454 projectDefaultSelection: resolveDefaultableModelSelection(changedConfig, selection, null),
455 stickySelection: null,
456 modelOptions: options,
457 }),
458 ).toBe(selection);
459​
460 const [restored] = buildModelOptions(config, selection, undefined, null);
461 expect(isModelSelectionUnavailable(config, selection, null)).toBe(false);
462 expect(restored?.isUnavailable).not.toBe(true);
463 expect(restored?.selection).toBe(selection);
464 expect(resolveDefaultableModelSelection(config, selection, null)).toBe(selection);
465 expect(
466 buildModelOptions(config, null, undefined, null)[0]?.selection.options,
467 ).toBeUndefined();
468 });
469​
470 it("uses configured instance metadata when provider status is missing", () => {
471 const missingStatusConfig = {
472 providers: [],
473 settings: {
474 providerInstances: {
475 [selection.instanceId]: { driver: "antigravity", displayName: "Google Work" },
476 },
477 },
478 } as unknown as ServerConfig;
479​
480 expect(resolveDefaultableModelSelection(missingStatusConfig, selection, null)).toBe(
481 selection,
482 );
483 expect(isModelSelectionUnavailable(missingStatusConfig, selection, null)).toBe(true);
484 expect(buildModelOptions(missingStatusConfig, selection, undefined, null)).toMatchObject([
485 {
486 providerDriver: "antigravity",
487 providerLabel: "Google Work",
488 isUnavailable: true,
489 selection,
490 },
491 ]);
492 });
493​
494 it("keeps offline selections without assuming that an unknown instance is Antigravity", () => {
495 const unknownConfig = { ...config, providers: [] };
496​
497 expect(resolveDefaultableModelSelection(null, selection, null)).toBe(selection);
498 expect(isModelSelectionUnavailable(null, selection, null)).toBe(false);
499 expect(buildModelOptions(null, selection, undefined, null)[0]?.selection).toBe(selection);
500 expect(buildModelOptions(null, selection, undefined, null)[0]?.isUnavailable).not.toBe(true);
501 expect(isModelSelectionUnavailable(unknownConfig, selection, null)).toBe(false);
502 expect(resolveSelectableModelSelection(unknownConfig, selection, null)).toBeNull();
503 });
504 });
505​
506 it("keeps legacy models out of implicit defaults", () => {
507 const config = {
508 providers: [
509 {
510 instanceId: "codex",
511 driver: "codex",
512 displayName: "Codex",
513 enabled: true,
514 installed: true,
515 auth: { status: "authenticated" },
516 models: [
517 { slug: "gpt-5.6-sol", name: "GPT-5.6 Sol", isCustom: false, capabilities: null },
518 {
519 slug: "gpt-5.4",
520 name: "GPT-5.4",
521 isCustom: false,
522 isLegacy: true,
523 capabilities: null,
524 },
525 ],
526 },
527 ],
528 } as unknown as ServerConfig;
529​
530 const current = { instanceId: ProviderInstanceId.make("codex"), model: "gpt-5.6-sol" };
531 const legacy = { instanceId: ProviderInstanceId.make("codex"), model: "gpt-5.4" };
532​
533 expect(resolveDefaultableModelSelection(config, current, null)).toBe(current);
534 expect(resolveDefaultableModelSelection(config, legacy, null)).toBeNull();
535 expect(resolveDefaultableModelSelection(null, legacy, null)).toBe(legacy);
536 });
537​
538 it("resolves new tasks from draft, project, sticky, then provider defaults", () => {
539 const draft = { instanceId: ProviderInstanceId.make("codex"), model: "draft" };
540 const project = { instanceId: ProviderInstanceId.make("codex"), model: "project" };
541 const sticky = { instanceId: ProviderInstanceId.make("codex"), model: "sticky" };
542 const providerDefault = {
543 selection: { instanceId: ProviderInstanceId.make("codex"), model: "default" },
544 isDefault: true,
545 } as ModelOption;
546 const resolve = (
547 draftSelection: ModelSelection | null,
548 projectDefaultSelection: ModelSelection | null,
549 stickySelection: ModelSelection | null,
550 ) =>
551 resolveNewTaskModelSelection({
552 draftSelection,
553 projectDefaultSelection,
554 stickySelection,
555 modelOptions: [providerDefault],
556 });
557​
558 expect(resolve(draft, project, sticky)).toBe(draft);
559 expect(resolve(null, project, sticky)).toBe(project);
560 expect(resolve(null, null, sticky)).toBe(sticky);
561 expect(resolve(null, null, null)).toBe(providerDefault.selection);
562​
563 const unavailable = { ...providerDefault, isUnavailable: true };
564 expect(
565 resolveNewTaskModelSelection({
566 draftSelection: null,
567 projectDefaultSelection: null,
568 stickySelection: null,
569 modelOptions: [unavailable],
570 }),
571 ).toBeNull();
572 });
573​
574 describe("project-aware enablement", () => {
575 const projectId = ProjectId.make("project-a");
576 const instanceId = ProviderInstanceId.make("codex_work");
577​
578 function configWith(settings: ServerSettings): ServerConfig {
579 return {
580 providers: [
581 {
582 instanceId,
583 driver: "codex",
584 displayName: "Codex Work",
585 enabled: true,
586 installed: true,
587 auth: { status: "authenticated" },
588 models: [{ slug: "model-a", name: "Model A", isCustom: false, capabilities: null }],
589 },
590 ],
591 settings,
592 } as unknown as ServerConfig;
593 }
594​
595 it("refuses to send a non-Antigravity model the project turned off", () => {
596 const settings: ServerSettings = {
597 ...DEFAULT_SERVER_SETTINGS,
598 providerInstances: {
599 [instanceId]: { driver: ProviderDriverKind.make("codex"), enabled: true },
600 },
601 projectSettingsOverrides: {
602 [projectId]: { providerInstances: { [instanceId]: { enabled: false } } },
603 },
604 };
605 const selection = { instanceId, model: "model-a" };
606​
607 expect(isModelSelectionUnavailable(configWith(settings), selection, projectId)).toBe(true);
608 expect(isModelSelectionUnavailable(configWith(settings), selection, null)).toBe(false);
609 });
610​
611 it("hides an instance the project disables even though the machine enables it", () => {
612 const settings: ServerSettings = {
613 ...DEFAULT_SERVER_SETTINGS,
614 providerInstances: {
615 [instanceId]: { driver: ProviderDriverKind.make("codex"), enabled: true },
616 },
617 projectSettingsOverrides: {
618 [projectId]: { providerInstances: { [instanceId]: { enabled: false } } },
619 },
620 };
621​
622 expect(buildModelOptions(configWith(settings), null, undefined, projectId)).toEqual([]);
623 });
624​
625 it("hides a machine-disabled instance for a project with no override, even though another project's keep-alive left it running", () => {
626 const otherProjectId = ProjectId.make("project-b");
627 const settings: ServerSettings = {
628 ...DEFAULT_SERVER_SETTINGS,
629 providerInstances: {
630 [instanceId]: { driver: ProviderDriverKind.make("codex"), enabled: false },
631 },
632 projectSettingsOverrides: {
633 [otherProjectId]: { providerInstances: { [instanceId]: { enabled: true } } },
634 },
635 };
636​
637 expect(buildModelOptions(configWith(settings), null, undefined, projectId)).toEqual([]);
638 });
639​
640 it("refuses to send an instance the project merely inherits as disabled, not just an explicit override", () => {
641 const settings: ServerSettings = {
642 ...DEFAULT_SERVER_SETTINGS,
643 providerInstances: {
644 [instanceId]: { driver: ProviderDriverKind.make("codex"), enabled: false },
645 },
646 };
647 const selection = { instanceId, model: "model-a" };
648​
649 expect(isModelSelectionUnavailable(configWith(settings), selection, projectId)).toBe(true);
650 expect(
651 describeUnavailableModelSelection(configWith(settings), selection, projectId).title,
652 ).toBe("Model turned off for this project");
653 expect(isModelSelectionUnavailable(configWith(settings), selection, null)).toBe(true);
654 });
655​
656 it("marks a project-disabled instance's fallback selection unavailable instead of reviving it", () => {
657 const settings: ServerSettings = {
658 ...DEFAULT_SERVER_SETTINGS,
659 providerInstances: {
660 [instanceId]: { driver: ProviderDriverKind.make("codex"), enabled: true },
661 },
662 projectSettingsOverrides: {
663 [projectId]: { providerInstances: { [instanceId]: { enabled: false } } },
664 },
665 };
666 const fallback = { instanceId, model: "model-a" };
667​
668 const options = buildModelOptions(configWith(settings), fallback, undefined, projectId);
669​
670 expect(options).toHaveLength(1);
671 expect(options[0]).toMatchObject({ selection: fallback, isUnavailable: true });
672 });
673​
674 it("rejects a project-disabled antigravity instance's selection instead of keeping it", () => {
675 const antigravityInstanceId = ProviderInstanceId.make("antigravity_work");
676 const config = {
677 providers: [
678 {
679 instanceId: antigravityInstanceId,
680 driver: "antigravity",
681 displayName: "Antigravity Work",
682 enabled: true,
683 installed: true,
684 auth: { status: "authenticated" },
685 availability: "available",
686 models: [{ slug: "model-a", name: "Model A", isCustom: false, capabilities: null }],
687 },
688 ],
689 settings: {
690 ...DEFAULT_SERVER_SETTINGS,
691 providerInstances: {
692 [antigravityInstanceId]: {
693 driver: ProviderDriverKind.make("antigravity"),
694 enabled: true,
695 },
696 },
697 projectSettingsOverrides: {
698 [projectId]: { providerInstances: { [antigravityInstanceId]: { enabled: false } } },
699 },
700 },
701 } as unknown as ServerConfig;
702 const selection = { instanceId: antigravityInstanceId, model: "model-a" };
703​
704 expect(resolveSelectableModelSelection(config, selection, projectId)).toBeNull();
705 expect(resolveSelectableModelSelection(config, selection, null)).toBe(selection);
706 expect(
707 resolveSelectableModelSelection({ ...config, providers: [] }, selection, projectId),
708 ).toBeNull();
709 });
710 });
711});
712​
713describe("synchronized mobile model visibility", () => {
714 const provider = ProviderInstanceId.make("codex_work");
715 const other = ProviderInstanceId.make("codex_personal");
716 const models = [
717 { slug: "hidden", name: "Hidden", isCustom: false, capabilities: null },
718 { slug: "first", name: "First", isCustom: false, capabilities: null },
719 { slug: "second", name: "Second", isCustom: false, capabilities: null },
720 { slug: "custom", name: "Custom", isCustom: true, capabilities: null },
721 ];
722 const config = {
723 settings: {
724 ...DEFAULT_SERVER_SETTINGS,
725 providerInstances: {
726 [provider]: { driver: ProviderDriverKind.make("codex"), enabled: true },
727 [other]: { driver: ProviderDriverKind.make("codex"), enabled: true },
728 },
729 providerModelPreferences: {
730 [provider]: {
731 hiddenModels: ["hidden", "custom", "missing"],
732 modelOrder: ["second", "first"],
733 },
734 },
735 },
736 providers: [provider, other].map((instanceId) => ({
737 instanceId,
738 continuation: { groupKey: instanceId },
739 driver: "codex",
740 enabled: true,
741 installed: true,
742 auth: { status: "authenticated" },
743 models,
744 })),
745 } as unknown as ServerConfig;
746​
747 it("applies server visibility and ordering independently to each provider instance", () => {
748 const options = buildModelOptions(config, null, undefined, null);
749 expect(
750 options
751 .filter((option) => option.providerKey === provider)
752 .map((option) => option.selection.model),
753 ).toEqual(["second", "first", "custom"]);
754 expect(
755 options
756 .filter((option) => option.providerKey === other)
757 .map((option) => option.selection.model),
758 ).toEqual(["hidden", "first", "second", "custom"]);
759 });
760​
761 it("exposes hidden built-ins for management without offering custom visibility controls", () => {
762 const options = buildModelOptions(
763 config,
764 { instanceId: provider, model: "hidden" },
765 provider,
766 null,
767 true,
768 );
769 expect(options.map((option) => option.selection.model)).toEqual([
770 "second",
771 "first",
772 "hidden",
773 "custom",
774 ]);
775 expect(options.find((option) => option.selection.model === "hidden")).toMatchObject({
776 isHidden: true,
777 canHide: true,
778 });
779 expect(options.find((option) => option.selection.model === "custom")).toMatchObject({
780 isHidden: false,
781 canHide: false,
782 });
783 });
784​
785 it.each(["hidden", "missing"])("does not resurrect the hidden fallback %s", (model) => {
786 expect(
787 buildModelOptions(config, { instanceId: provider, model }, provider, null).map(
788 (option) => option.selection.model,
789 ),
790 ).toEqual(["second", "first", "custom"]);
791 });
792​
793 // regression: #16816 sync model favorites and visibility across clients
794 it("keeps custom models visible and restores built-in models when the server clears hidden models", () => {
795 expect(
796 buildModelOptions(config, { instanceId: provider, model: "custom" }, provider, null).map(
797 (option) => option.selection.model,
798 ),
799 ).toContain("custom");
800 const cleared = { ...config, settings: { ...config.settings, providerModelPreferences: {} } };
801 expect(
802 buildModelOptions(cleared, null, provider, null).map((option) => option.selection.model),
803 ).toEqual(["hidden", "first", "second", "custom"]);
804 });
805​
806 describe("managed provider policy", () => {
807 const config = {
808 providers: [
809 {
810 instanceId: "codex",
811 driver: "codex",
812 enabled: true,
813 installed: true,
814 auth: { status: "authenticated" },
815 models: [{ slug: "gpt-5.6-sol", name: "GPT", isCustom: false, capabilities: null }],
816 },
817 {
818 instanceId: "claudeAgent",
819 driver: "claudeAgent",
820 enabled: true,
821 installed: true,
822 auth: { status: "authenticated" },
823 models: [
824 { slug: "claude-sonnet-5", name: "Sonnet", isCustom: false, capabilities: null },
825 ],
826 },
827 ],
828 managedPolicy: {
829 organization: "Example Corp",
830 lockedSettings: [],
831 limits: { providers: { allowedDrivers: [ProviderDriverKind.make("codex")] } },
832 },
833 } as unknown as ServerConfig;
834 const disallowed = {
835 instanceId: ProviderInstanceId.make("claudeAgent"),
836 model: "claude-sonnet-5",
837 };
838​
839 it("leaves drivers the policy does not allow out of the picker", () => {
840 expect(
841 buildModelOptions(config, null, undefined, null).map((option) => option.providerKey),
842 ).toEqual(["codex"]);
843 });
844​
845 it("marks a thread's selection on a disallowed driver unavailable with the policy reason", () => {
846 expect(
847 buildModelOptions(config, disallowed, undefined, null).find(
848 (option) => option.providerKey === "claudeAgent",
849 )?.isUnavailable,
850 ).toBe(true);
851 expect(isModelSelectionUnavailable(config, disallowed, null)).toBe(true);
852 expect(describeUnavailableModelSelection(config, disallowed, null)).toEqual({
853 title: "Provider not allowed",
854 detail: "Example Corp's policy does not allow the Claude provider. Choose another model.",
855 });
856 });
857 });
858});