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