1import { ProviderDriverKind, ProviderInstanceId, type ServerProvider } from "@t3tools/contracts";
2import { describe, expect, it } from "vite-plus/test";
3import {
4 applyProviderInstanceSettings,
5 deriveProviderEntriesByEnvironment,
6 deriveProviderInstanceEntries,
7 getDefaultProviderInstanceModel,
8 isProviderInstancePickerReady,
9 isProviderInstancePickerVisible,
10 resolveDefaultProviderModelSelection,
11 resolveProviderCatalogAvailability,
12 resolveSelectableProviderInstance,
13} from "./providerInstances";
14
15function provider(input: {
16 provider: ProviderDriverKind;
17 instanceId: string;
18 enabled?: boolean;
19 availability?: ServerProvider["availability"];
20 displayName?: string;
21 accentColor?: string;
22 status?: ServerProvider["status"];
23 models?: ServerProvider["models"];
24 supportsTextGeneration?: boolean;
25}): ServerProvider {
26 return {
27 instanceId: ProviderInstanceId.make(input.instanceId),
28 driver: input.provider,
29 ...(input.supportsTextGeneration === undefined
30 ? {}
31 : { supportsTextGeneration: input.supportsTextGeneration }),
32 ...(input.displayName ? { displayName: input.displayName } : {}),
33 ...(input.accentColor ? { accentColor: input.accentColor } : {}),
34 enabled: input.enabled ?? true,
35 installed: true,
36 version: null,
37 status: input.status ?? "ready",
38 ...(input.availability ? { availability: input.availability } : {}),
39 auth: { status: "authenticated" },
40 checkedAt: "2026-01-01T00:00:00.000Z",
41 models: input.models ?? [],
42 slashCommands: [],
43 skills: [],
44 };
45}
46
47const model = (slug: string, isCustom = false, isDefault = false) => ({
48 slug,
49 name: slug,
50 isCustom,
51 ...(isDefault ? { isDefault: true } : {}),
52 capabilities: {},
53});
54
55describe("isProviderInstancePickerReady", () => {
56 it("rejects a disabled instance even while its last probe status is ready", () => {
57 const [entry] = deriveProviderInstanceEntries([
58 provider({
59 provider: ProviderDriverKind.make("codex"),
60 instanceId: "codex",
61 enabled: false,
62 }),
63 ]);
64
65 expect(entry?.status).toBe("ready");
66 expect(entry && isProviderInstancePickerReady(entry)).toBe(false);
67 });
68
69 it("accepts an enabled, available, ready instance", () => {
70 const [entry] = deriveProviderInstanceEntries([
71 provider({ provider: ProviderDriverKind.make("codex"), instanceId: "codex" }),
72 ]);
73
74 expect(entry && isProviderInstancePickerReady(entry)).toBe(true);
75 });
76});
77
78describe("resolveProviderCatalogAvailability", () => {
79 const [entry] = deriveProviderInstanceEntries([
80 provider({ provider: ProviderDriverKind.make("codex"), instanceId: "codex" }),
81 ]);
82
83 it("keeps the initial reactive config state distinct from an empty catalogue", () => {
84 expect(
85 resolveProviderCatalogAvailability({
86 catalogLoaded: false,
87 entries: [],
88 selectedEntry: undefined,
89 }),
90 ).toBe("loading");
91 expect(
92 resolveProviderCatalogAvailability({
93 catalogLoaded: true,
94 entries: [],
95 selectedEntry: undefined,
96 }),
97 ).toBe("unconfigured");
98 });
99
100 it("distinguishes a selected provider from configured but unusable providers", () => {
101 expect(
102 resolveProviderCatalogAvailability({
103 catalogLoaded: true,
104 entries: entry ? [entry] : [],
105 selectedEntry: entry,
106 }),
107 ).toBe("ready");
108 expect(
109 resolveProviderCatalogAvailability({
110 catalogLoaded: true,
111 entries: entry ? [entry] : [],
112 selectedEntry: undefined,
113 }),
114 ).toBe("unavailable");
115 });
116});
117
118describe("isProviderInstancePickerVisible", () => {
119 it("keeps enabled instances in the rail and removes disabled instances", () => {
120 const [enabledEntry, disabledEntry] = deriveProviderInstanceEntries([
121 provider({ provider: ProviderDriverKind.make("codex"), instanceId: "codex" }),
122 provider({
123 provider: ProviderDriverKind.make("claudeAgent"),
124 instanceId: "claudeAgent",
125 enabled: false,
126 }),
127 ]);
128
129 expect(enabledEntry && isProviderInstancePickerVisible(enabledEntry)).toBe(true);
130 expect(disabledEntry && isProviderInstancePickerVisible(disabledEntry)).toBe(false);
131 });
132});
133
134describe("applyProviderInstanceSettings", () => {
135 it("uses settings when a streamed snapshot still reports a disabled default as enabled", () => {
136 const entries = deriveProviderInstanceEntries([
137 provider({ provider: ProviderDriverKind.make("codex"), instanceId: "codex" }),
138 ]);
139 const [entry] = applyProviderInstanceSettings(
140 entries,
141 {
142 providerInstances: {
143 [ProviderInstanceId.make("codex")]: {
144 driver: ProviderDriverKind.make("codex"),
145 enabled: false,
146 },
147 },
148 projectSettingsOverrides: {},
149 },
150 null,
151 );
152
153 expect(entry?.enabled).toBe(false);
154 });
155
156 it("treats a removed custom instance snapshot as disabled", () => {
157 const entries = deriveProviderInstanceEntries([
158 provider({
159 provider: ProviderDriverKind.make("claudeAgent"),
160 instanceId: "claude_work",
161 }),
162 ]);
163 const [entry] = applyProviderInstanceSettings(
164 entries,
165 {
166 providerInstances: {},
167 projectSettingsOverrides: {},
168 },
169 null,
170 );
171
172 expect(entry?.enabled).toBe(false);
173 });
174
175 it.each(["constructor", "toString"])(
176 "treats a removed custom instance named %s as disabled",
177 (instanceId) => {
178 const entries = deriveProviderInstanceEntries([
179 provider({
180 provider: ProviderDriverKind.make("claudeAgent"),
181 instanceId,
182 }),
183 ]);
184 const [entry] = applyProviderInstanceSettings(
185 entries,
186 {
187 providerInstances: {},
188 projectSettingsOverrides: {},
189 },
190 null,
191 );
192
193 expect(entry?.enabled).toBe(false);
194 },
195 );
196
197 it("uses settings for a configured custom instance named constructor", () => {
198 const instanceId = ProviderInstanceId.make("constructor");
199 const entries = deriveProviderInstanceEntries([
200 provider({
201 provider: ProviderDriverKind.make("claudeAgent"),
202 instanceId,
203 }),
204 ]);
205 const [entry] = applyProviderInstanceSettings(
206 entries,
207 {
208 providerInstances: {
209 [instanceId]: {
210 driver: ProviderDriverKind.make("claudeAgent"),
211 enabled: false,
212 },
213 },
214 projectSettingsOverrides: {},
215 },
216 null,
217 );
218
219 expect(entry?.enabled).toBe(false);
220 });
221
222 it("treats a removed default instance for a fork driver as disabled", () => {
223 const driver = ProviderDriverKind.make("constructor");
224 const entries = deriveProviderInstanceEntries([
225 provider({
226 provider: driver,
227 instanceId: "constructor",
228 }),
229 ]);
230 const [entry] = applyProviderInstanceSettings(
231 entries,
232 {
233 providerInstances: {},
234 projectSettingsOverrides: {},
235 },
236 null,
237 );
238
239 expect(entry?.isDefault).toBe(true);
240 expect(entry?.enabled).toBe(false);
241 });
242
243 it("uses the driver default for an unconfigured built-in default instance", () => {
244 const entries = deriveProviderInstanceEntries([
245 provider({ provider: ProviderDriverKind.make("codex"), instanceId: "codex", enabled: false }),
246 provider({ provider: ProviderDriverKind.make("grok"), instanceId: "grok", enabled: true }),
247 ]);
248 const [codex, grok] = applyProviderInstanceSettings(
249 entries,
250 {
251 providerInstances: {},
252 projectSettingsOverrides: {},
253 },
254 null,
255 );
256
257 // Settings decide over a stale probe: Codex starts on, Grok starts off.
258 expect(codex?.enabled).toBe(true);
259 expect(grok?.enabled).toBe(false);
260 });
261});
262
263describe("deriveProviderInstanceEntries", () => {
264 it("uses explicit instance id and driver kind from the snapshot", () => {
265 const snapshot = provider({
266 provider: ProviderDriverKind.make("codex"),
267 instanceId: "codex_personal",
268 });
269 const [entry] = deriveProviderInstanceEntries([snapshot]);
270
271 expect(entry?.instanceId).toBe("codex_personal");
272 expect(entry?.driverKind).toBe("codex");
273 expect(entry?.isDefault).toBe(false);
274 });
275});
276
277describe("deriveProviderEntriesByEnvironment", () => {
278 it("resolves registry branding from each environment's settings", () => {
279 const instanceId = "custom-acp";
280 const snapshot = provider({ provider: ProviderDriverKind.make("acpRegistry"), instanceId });
281 const byEnvironment = deriveProviderEntriesByEnvironment(
282 ["devin", "other-agent"].map(
283 (agentId) =>
284 [
285 agentId,
286 [snapshot],
287 {
288 providerInstances: {
289 [instanceId]: {
290 driver: ProviderDriverKind.make("acpRegistry"),
291 enabled: true,
292 config: { agentId, registryIconUrl: `https://example.com/${agentId}.svg` },
293 },
294 },
295 projectSettingsOverrides: {},
296 },
297 ] as const,
298 ),
299 );
300 expect(byEnvironment.get("devin")?.get(instanceId)?.acpRegistryAgentId).toBe("devin");
301 expect(byEnvironment.get("devin")?.get(instanceId)?.acpRegistryIconUrl).toBe(
302 "https://example.com/devin.svg",
303 );
304 expect(byEnvironment.get("other-agent")?.get(instanceId)?.acpRegistryAgentId).toBe(
305 "other-agent",
306 );
307 });
308
309 it("keeps same-id default instances distinct per environment", () => {
310 const byEnvironment = deriveProviderEntriesByEnvironment([
311 [
312 "local",
313 [
314 provider({
315 provider: ProviderDriverKind.make("claude"),
316 instanceId: "claude",
317 displayName: "Claude Local",
318 accentColor: "#112233",
319 }),
320 ],
321 ],
322 [
323 "remote",
324 [
325 provider({
326 provider: ProviderDriverKind.make("claude"),
327 instanceId: "claude",
328 displayName: "Claude Remote",
329 accentColor: "#445566",
330 }),
331 ],
332 ],
333 ]);
334
335 expect(byEnvironment.get("local")?.get("claude")?.displayName).toBe("Claude Local");
336 expect(byEnvironment.get("local")?.get("claude")?.accentColor).toBe("#112233");
337 expect(byEnvironment.get("remote")?.get("claude")?.displayName).toBe("Claude Remote");
338 expect(byEnvironment.get("remote")?.get("claude")?.accentColor).toBe("#445566");
339 });
340
341 it("never falls back to another environment's instances", () => {
342 const byEnvironment = deriveProviderEntriesByEnvironment([
343 ["local", [provider({ provider: ProviderDriverKind.make("codex"), instanceId: "codex" })]],
344 ["empty", []],
345 ]);
346
347 expect(byEnvironment.get("empty")?.get("codex")).toBeUndefined();
348 // Every environment gets its own bucket, so an absent lookup is a real
349 // "this environment has no such instance", not a missing key.
350 expect(byEnvironment.get("empty")?.size).toBe(0);
351 });
352});
353
354describe("resolveSelectableProviderInstance", () => {
355 it("returns the requested instance when it is enabled and available", () => {
356 const requested = ProviderInstanceId.make("claude_work");
357 const providers = [
358 provider({ provider: ProviderDriverKind.make("codex"), instanceId: "codex" }),
359 provider({ provider: ProviderDriverKind.make("claudeAgent"), instanceId: requested }),
360 ];
361
362 expect(resolveSelectableProviderInstance(providers, requested)).toBe(requested);
363 });
364
365 it("falls back to the first enabled and available instance", () => {
366 const disabled = ProviderInstanceId.make("codex");
367 const fallback = ProviderInstanceId.make("claudeAgent");
368 const providers = [
369 provider({
370 provider: ProviderDriverKind.make("codex"),
371 instanceId: disabled,
372 enabled: false,
373 }),
374 provider({ provider: ProviderDriverKind.make("claudeAgent"), instanceId: fallback }),
375 ];
376
377 expect(resolveSelectableProviderInstance(providers, disabled)).toBe(fallback);
378 });
379
380 it("prefers a ready instance over an enabled one whose driver cannot start", () => {
381 const notInstalled = ProviderInstanceId.make("codex");
382 const ready = ProviderInstanceId.make("claudeAgent");
383 const providers = [
384 provider({
385 provider: ProviderDriverKind.make("codex"),
386 instanceId: notInstalled,
387 status: "error",
388 }),
389 provider({ provider: ProviderDriverKind.make("claudeAgent"), instanceId: ready }),
390 ];
391
392 expect(resolveSelectableProviderInstance(providers, undefined)).toBe(ready);
393 });
394
395 it("prefers an unprobed (warning) instance over one whose probe errored", () => {
396 const notInstalled = ProviderInstanceId.make("codex");
397 const unprobed = ProviderInstanceId.make("claudeAgent");
398 const providers = [
399 provider({
400 provider: ProviderDriverKind.make("codex"),
401 instanceId: notInstalled,
402 status: "error",
403 }),
404 provider({
405 provider: ProviderDriverKind.make("claudeAgent"),
406 instanceId: unprobed,
407 status: "warning",
408 }),
409 ];
410
411 expect(resolveSelectableProviderInstance(providers, undefined)).toBe(unprobed);
412 });
413
414 it("keeps a requested instance even when its probe errored", () => {
415 const requested = ProviderInstanceId.make("codex");
416 const providers = [
417 provider({
418 provider: ProviderDriverKind.make("codex"),
419 instanceId: requested,
420 status: "error",
421 }),
422 provider({ provider: ProviderDriverKind.make("claudeAgent"), instanceId: "claudeAgent" }),
423 ];
424
425 expect(resolveSelectableProviderInstance(providers, requested)).toBe(requested);
426 });
427
428 it("does not invent an errored instance as a new-user default", () => {
429 const notInstalled = ProviderInstanceId.make("codex");
430 const providers = [
431 provider({
432 provider: ProviderDriverKind.make("codex"),
433 instanceId: notInstalled,
434 status: "error",
435 }),
436 ];
437
438 expect(resolveSelectableProviderInstance(providers, undefined)).toBeUndefined();
439 });
440
441 it("does not return disabled, unavailable, or unknown instances when none are sendable", () => {
442 const disabled = ProviderInstanceId.make("codex");
443 const unavailable = ProviderInstanceId.make("claudeAgent");
444 const unknown = ProviderInstanceId.make("removed_instance");
445 const providers = [
446 provider({
447 provider: ProviderDriverKind.make("codex"),
448 instanceId: disabled,
449 enabled: false,
450 }),
451 provider({
452 provider: ProviderDriverKind.make("claudeAgent"),
453 instanceId: unavailable,
454 availability: "unavailable",
455 }),
456 ];
457
458 expect(resolveSelectableProviderInstance(providers, disabled)).toBeUndefined();
459 expect(resolveSelectableProviderInstance(providers, unavailable)).toBeUndefined();
460 expect(resolveSelectableProviderInstance(providers, unknown)).toBeUndefined();
461 });
462});
463
464describe("getDefaultProviderInstanceModel", () => {
465 it("uses the instance's own models, not the default instance of the kind", () => {
466 const providers = [
467 provider({
468 provider: ProviderDriverKind.make("claudeAgent"),
469 instanceId: "claude_openrouter",
470 models: [model("openai/gpt-5.5", true), model("claude-opus-4-8")],
471 }),
472 provider({
473 provider: ProviderDriverKind.make("claudeAgent"),
474 instanceId: "claudeAgent",
475 models: [model("claude-sonnet-5")],
476 }),
477 ];
478
479 expect(
480 getDefaultProviderInstanceModel(providers, ProviderInstanceId.make("claude_openrouter")),
481 ).toBe("claude-opus-4-8");
482 });
483
484 it("falls back to the driver default when the instance reports no models", () => {
485 const providers = [
486 provider({ provider: ProviderDriverKind.make("claudeAgent"), instanceId: "claudeAgent" }),
487 ];
488
489 const resolved = getDefaultProviderInstanceModel(
490 providers,
491 ProviderInstanceId.make("claudeAgent"),
492 );
493 expect(typeof resolved).toBe("string");
494 expect(resolved?.length).toBeGreaterThan(0);
495 });
496
497 it("honors the instance's declared default before model-list order", () => {
498 const providers = [
499 provider({
500 provider: ProviderDriverKind.make("claudeAgent"),
501 instanceId: "claudeAgent",
502 models: [model("claude-sonnet-5"), model("claude-opus-4-8", false, true)],
503 }),
504 ];
505
506 expect(getDefaultProviderInstanceModel(providers, ProviderInstanceId.make("claudeAgent"))).toBe(
507 "claude-opus-4-8",
508 );
509 });
510
511 it("returns undefined for an unknown instance", () => {
512 expect(
513 getDefaultProviderInstanceModel([], ProviderInstanceId.make("removed_instance")),
514 ).toBeUndefined();
515 });
516});
517
518describe("resolveDefaultProviderModelSelection", () => {
519 it.each([
520 ["codex", "codex", "gpt-5.6"],
521 ["claudeAgent", "claudeAgent", "claude-fable-5"],
522 ["cursor", "cursor", "composer-2"],
523 ])("uses the only available %s instance", (driver, instanceId, modelSlug) => {
524 const providers = [
525 provider({
526 provider: ProviderDriverKind.make(driver),
527 instanceId,
528 models: [model(modelSlug, false, true)],
529 }),
530 ];
531
532 expect(resolveDefaultProviderModelSelection(providers, null)).toEqual({
533 instanceId,
534 model: modelSlug,
535 });
536 });
537
538 it("preserves a valid stored selection including its options", () => {
539 const providers = [
540 provider({
541 provider: ProviderDriverKind.make("claudeAgent"),
542 instanceId: "claudeAgent",
543 models: [model("claude-opus-4-8")],
544 }),
545 ];
546 const stored = {
547 instanceId: ProviderInstanceId.make("claudeAgent"),
548 model: "custom-model",
549 options: [{ id: "effort", value: "high" }],
550 };
551
552 expect(resolveDefaultProviderModelSelection(providers, stored)).toBe(stored);
553 });
554
555 it("replaces a stale stored instance with the first ready instance and its model", () => {
556 const providers = [
557 provider({
558 provider: ProviderDriverKind.make("codex"),
559 instanceId: "codex",
560 status: "warning",
561 models: [model("gpt-5.6")],
562 }),
563 provider({
564 provider: ProviderDriverKind.make("claudeAgent"),
565 instanceId: "claudeAgent",
566 models: [model("claude-opus-4-8", false, true)],
567 }),
568 ];
569
570 expect(
571 resolveDefaultProviderModelSelection(providers, {
572 instanceId: ProviderInstanceId.make("removed-provider"),
573 model: "stale-model",
574 }),
575 ).toEqual({ instanceId: "claudeAgent", model: "claude-opus-4-8" });
576 });
577
578 it.each([{ enabled: false }, { availability: "unavailable" as const }])(
579 "replaces an unavailable stored instance deterministically",
580 (requestedState) => {
581 const providers = [
582 provider({
583 provider: ProviderDriverKind.make("codex"),
584 instanceId: "codex",
585 models: [model("gpt-5.6")],
586 ...requestedState,
587 }),
588 provider({
589 provider: ProviderDriverKind.make("claudeAgent"),
590 instanceId: "claudeAgent",
591 models: [model("claude-opus-4-8", false, true)],
592 }),
593 ];
594
595 expect(
596 resolveDefaultProviderModelSelection(providers, {
597 instanceId: ProviderInstanceId.make("codex"),
598 model: "gpt-5.6",
599 }),
600 ).toEqual({ instanceId: "claudeAgent", model: "claude-opus-4-8" });
601 },
602 );
603
604 it("returns no selection for empty, disabled, unavailable, or error-only profiles", () => {
605 expect(resolveDefaultProviderModelSelection([], null)).toBeNull();
606 expect(
607 resolveDefaultProviderModelSelection(
608 [
609 provider({
610 provider: ProviderDriverKind.make("codex"),
611 instanceId: "codex",
612 enabled: false,
613 }),
614 ],
615 null,
616 ),
617 ).toBeNull();
618 expect(
619 resolveDefaultProviderModelSelection(
620 [
621 provider({
622 provider: ProviderDriverKind.make("codex"),
623 instanceId: "codex",
624 availability: "unavailable",
625 }),
626 ],
627 null,
628 ),
629 ).toBeNull();
630 expect(
631 resolveDefaultProviderModelSelection(
632 [
633 provider({
634 provider: ProviderDriverKind.make("codex"),
635 instanceId: "codex",
636 status: "error",
637 }),
638 ],
639 null,
640 ),
641 ).toBeNull();
642 });
643});
644
645describe("provider icon metadata", () => {
646 it("clears stale registry branding when an instance switches to a local command", () => {
647 const instanceId = ProviderInstanceId.make("custom-acp");
648 const driver = ProviderDriverKind.make("acpRegistry");
649 const iconUrl = "https://cdn.agentclientprotocol.com/registry/icons/swe-agent.svg";
650 const snapshots = deriveProviderInstanceEntries([
651 { ...provider({ provider: driver, instanceId }), iconUrl },
652 ]);
653 const registryConfig = { agentId: "swe-agent", registryIconUrl: iconUrl };
654 const brandedEntries = applyProviderInstanceSettings(
655 snapshots,
656 {
657 providerInstances: { [instanceId]: { driver, enabled: true, config: registryConfig } },
658 projectSettingsOverrides: {},
659 },
660 null,
661 );
662 expect(brandedEntries[0]?.acpRegistryAgentId).toBe("swe-agent");
663 expect(brandedEntries[0]?.acpRegistryIconUrl).toBe(iconUrl);
664
665 const [localEntry] = applyProviderInstanceSettings(
666 brandedEntries,
667 {
668 providerInstances: {
669 [instanceId]: {
670 driver,
671 enabled: false,
672 config: { ...registryConfig, source: "local", commandPath: "dsh" },
673 },
674 },
675 projectSettingsOverrides: {},
676 },
677 null,
678 );
679 expect(localEntry?.acpRegistryAgentId).toBeUndefined();
680 expect(localEntry?.acpRegistryIconUrl).toBeUndefined();
681 expect(localEntry?.enabled).toBe(false);
682 expect(localEntry?.snapshot).toBe(snapshots[0]?.snapshot);
683
684 const [restoredEntry] = applyProviderInstanceSettings(
685 localEntry ? [localEntry] : [],
686 {
687 providerInstances: { [instanceId]: { driver, enabled: true, config: registryConfig } },
688 projectSettingsOverrides: {},
689 },
690 null,
691 );
692 expect(restoredEntry?.acpRegistryAgentId).toBe("swe-agent");
693 expect(restoredEntry?.acpRegistryIconUrl).toBe(iconUrl);
694 expect(restoredEntry?.enabled).toBe(true);
695 });
696
697 it("retains server-published registry icons without local settings", () => {
698 const iconUrl = "https://cdn.agentclientprotocol.com/registry/icons/swe-agent.svg";
699 const [entry] = deriveProviderInstanceEntries([
700 {
701 ...provider({ provider: ProviderDriverKind.make("acpRegistry"), instanceId: "swe-remote" }),
702 iconUrl,
703 },
704 ]);
705 expect(entry?.acpRegistryIconUrl).toBe(iconUrl);
706 expect(entry?.driverKind).toBe("acpRegistry");
707 });
708});