From faf79f146462db502fc4bcf8bcaf12d8e15739e2 Mon Sep 17 00:00:00 2001 From: James Tatum Date: Fri, 30 Oct 2026 23:44:58 -0700 Subject: [PATCH 1/3] fix(web): show mixed automatic defaults across environments --- .../settings/ProjectDefaultsSettings.tsx | 37 ++++++-- apps/web/src/modelSelection.test.ts | 85 +++++++++++++++++++ apps/web/src/modelSelection.ts | 21 +++++ 3 files changed, 134 insertions(+), 9 deletions(-) diff --git a/apps/web/src/components/settings/ProjectDefaultsSettings.tsx b/apps/web/src/components/settings/ProjectDefaultsSettings.tsx index 43c4419962a02..5bbd3e05f0723 100644 --- a/apps/web/src/components/settings/ProjectDefaultsSettings.tsx +++ b/apps/web/src/components/settings/ProjectDefaultsSettings.tsx @@ -7,7 +7,9 @@ import { createModelSelection } from "@t3tools/shared/model"; import { resolveProjectSettings } from "@t3tools/shared/projectSettings"; import { useNavigate } from "@tanstack/react-router"; +import { mergeEnvironmentSettings } from "../../hooks/useSettings"; import { + defaultModelSelectionsAreMixed, getCustomModelOptionsByInstance, resolveEffectiveDefaultModelSelection, } from "../../modelSelection"; @@ -48,7 +50,7 @@ function isWorktreeSubmodules(value: string | null): value is WorktreeSubmodules } export function ProjectDefaultsSettings({ category }: { category: ProjectSettingsCategory }) { - const { scope, target, connectedEnvironments } = useSettingsScope(); + const { scope, target, targets, connectedEnvironments } = useSettingsScope(); const settings = useScopedSettings(); const updateSettings = useUpdateScopedSettings(); const navigate = useNavigate(); @@ -76,7 +78,15 @@ export function ProjectDefaultsSettings({ category }: { category: ProjectSetting selection?.model, ); const activeEntry = entries.find((entry) => entry.instanceId === selection?.instanceId); - const mixedModel = useScopedSettingsMixed(["defaultModelSelection"]); + const mixedModel = defaultModelSelectionsAreMixed( + targets.map((candidate) => ({ + settings: mergeEnvironmentSettings(candidate.settings, settings), + providers: providersByEnvironment.get(candidate.environmentId) ?? EMPTY_SERVER_PROVIDERS, + })), + ); + const automaticModel = + targets.length > 0 && + targets.every((candidate) => candidate.settings.defaultModelSelection === null); const mixedFallback = useScopedSettingsMixed(["fallbackModelSelection"]); const fallbackSelection = settings.fallbackModelSelection ?? selection; const fallbackModelOptions = getCustomModelOptionsByInstance( @@ -99,6 +109,9 @@ export function ProjectDefaultsSettings({ category }: { category: ProjectSetting : null; const modelDisabledReason = useScopedModelDisabledReason(settings, entries); + const effortDisabledReason = selection + ? modelDisabledReason(selection.instanceId, selection.model) + : null; const setModel = (value: ModelSelection | null) => { const reason = value ? modelDisabledReason(value.instanceId, value.model) : null; @@ -119,13 +132,15 @@ export function ProjectDefaultsSettings({ category }: { category: ProjectSetting description={ isProjectScope ? "Model for new threads in this project." - : !unavailable && - !mixedModel && - modelSource !== "project" && - settings.defaultModelSelection === null + : !unavailable && modelSource !== "project" && automaticModel ? "Default model for new threads, selected automatically. Projects can override it." : "Default model for new threads. Projects can override it." } + status={ + mixedModel + ? `${automaticModel ? "Automatic defaults differ." : "Defaults differ."} Select one environment or choose a shared model to edit effort.` + : effortDisabledReason + } resetAction={ settings.defaultModelSelection !== null ? ( setModel(null)} /> @@ -142,7 +157,9 @@ export function ProjectDefaultsSettings({ category }: { category: ProjectSetting instanceEntries={entries} modelOptionsByInstance={modelOptions} triggerClassName={SETTINGS_PICKER_TRIGGER_CLASSNAME} - {...(mixedModel ? { triggerLabel: "Mixed" } : {})} + {...(mixedModel + ? { triggerLabel: automaticModel ? "Automatic (mixed)" : "Mixed" } + : {})} getModelDisabledReason={modelDisabledReason} onOpenProviderSetup={(instanceId) => { if (representative) @@ -155,7 +172,7 @@ export function ProjectDefaultsSettings({ category }: { category: ProjectSetting setModel(createModelSelection(instanceId, model)) } /> - {!mixedModel ? ( + {!mixedModel && !effortDisabledReason ? ( ) : ( - No providers available + + {mixedModel ? "Mixed" : "No providers available"} + ) } /> diff --git a/apps/web/src/modelSelection.test.ts b/apps/web/src/modelSelection.test.ts index b5e9be7d9e011..ffaebf204d73b 100644 --- a/apps/web/src/modelSelection.test.ts +++ b/apps/web/src/modelSelection.test.ts @@ -17,6 +17,7 @@ import { providerInstanceEntriesFor, } from "./providerInstances"; import { + defaultModelSelectionsAreMixed, getAppModelOptionsForInstance, resolveAppModelSelectionForInstance, resolveAppModelSelectionState, @@ -73,6 +74,90 @@ function settingsWithProviderInstances(): UnifiedSettings { }; } +describe("scoped default model selections", () => { + // regression: #27997 equal automatic defaults can resolve to different providers + it("compares automatic defaults using each environment's models", () => { + const codex = { + settings: DEFAULT_UNIFIED_SETTINGS, + providers: [provider({ instanceId: "codex", models: ["gpt-6-astra"] })], + }; + const claude = { + settings: DEFAULT_UNIFIED_SETTINGS, + providers: [ + provider({ + instanceId: "claudeAgent", + provider: ProviderDriverKind.make("claudeAgent"), + models: ["claude-sonnet-4-6"], + }), + ], + }; + + expect(defaultModelSelectionsAreMixed([codex, claude])).toBe(true); + expect(defaultModelSelectionsAreMixed([claude, codex])).toBe(true); + expect(defaultModelSelectionsAreMixed([codex, codex])).toBe(false); + expect(defaultModelSelectionsAreMixed([claude])).toBe(false); + expect(defaultModelSelectionsAreMixed([])).toBe(false); + expect(defaultModelSelectionsAreMixed([codex, { ...codex, providers: [] }])).toBe(true); + expect( + defaultModelSelectionsAreMixed([ + codex, + { ...codex, providers: [provider({ instanceId: "codex", models: ["gpt-6-sol"] })] }, + ]), + ).toBe(true); + expect( + defaultModelSelectionsAreMixed([ + codex, + { + ...codex, + settings: { + ...codex.settings, + providerModelPreferences: { + [ProviderInstanceId.make("codex")]: { + hiddenModels: ["gpt-6-astra"], + modelOrder: [], + }, + }, + }, + providers: [provider({ instanceId: "codex", models: ["gpt-6-astra", "gpt-6-sol"] })], + }, + ]), + ).toBe(true); + }); + + it("keeps automatic, explicit, and different saved options distinct", () => { + const providers = [provider({ instanceId: "codex", models: ["gpt-6-astra"] })]; + const selected = { + providers, + settings: { + ...DEFAULT_UNIFIED_SETTINGS, + defaultModelSelection: createModelSelection( + ProviderInstanceId.make("codex"), + "gpt-6-astra", + ), + }, + }; + expect(defaultModelSelectionsAreMixed([selected, { ...selected }])).toBe(false); + expect( + defaultModelSelectionsAreMixed([selected, { providers, settings: DEFAULT_UNIFIED_SETTINGS }]), + ).toBe(true); + expect( + defaultModelSelectionsAreMixed([ + selected, + { + ...selected, + settings: { + ...selected.settings, + defaultModelSelection: { + ...selected.settings.defaultModelSelection, + options: [{ id: "reasoningEffort", value: "xhigh" }], + }, + }, + }, + ]), + ).toBe(true); + }); +}); + describe("instance-scoped model selection", () => { it("keeps custom models on the provider instance that declared them", () => { const providers = [ diff --git a/apps/web/src/modelSelection.ts b/apps/web/src/modelSelection.ts index db52028c397e0..69ba546469027 100644 --- a/apps/web/src/modelSelection.ts +++ b/apps/web/src/modelSelection.ts @@ -18,6 +18,7 @@ import { import { getComposerProviderState } from "./components/chat/composerProviderState"; import { UnifiedSettings } from "@t3tools/contracts/settings"; import * as Arr from "effect/Array"; +import * as Equal from "effect/Equal"; import * as Result from "effect/Result"; import { getDefaultServerModel, @@ -29,6 +30,7 @@ import { type ProviderInstanceEntry, deriveProviderInstanceEntries, NO_PROVIDER_MODEL_SELECTION, + resolveDefaultProviderModelSelection, } from "./providerInstances"; import { sortModelsForProviderInstance } from "./modelOrdering"; import { resolveModelShortName } from "@t3tools/client-runtime/provider-clients"; @@ -308,6 +310,25 @@ export function resolveEffectiveDefaultModelSelection( return model === null || model === selection.model ? selection : { ...selection, model }; } +export function defaultModelSelectionsAreMixed( + targets: ReadonlyArray<{ + settings: UnifiedSettings; + providers: ReadonlyArray; + }>, +): boolean { + const selections = targets.map(({ settings, providers }) => { + const stored = settings.defaultModelSelection; + const selection = resolveDefaultProviderModelSelection(providers, stored); + return { + stored, + effective: selection + ? resolveEffectiveDefaultModelSelection(settings, providers, selection) + : null, + }; + }); + return selections.some((selection) => !Equal.equals(selection, selections[0])); +} + export function getCustomModelOptionsByInstance( settings: UnifiedSettings, providers: ReadonlyArray, From 1daf7f0b1847e0266e66ab512a82d02f0ee2ebf1 Mon Sep 17 00:00:00 2001 From: James Tatum Date: Fri, 30 Oct 2026 23:56:43 -0700 Subject: [PATCH 2/3] fix(web): resolve default preferences per environment --- .../settings/ProjectDefaultsSettings.tsx | 6 +- apps/web/src/modelSelection.test.ts | 75 ++++++++++++------- apps/web/src/modelSelection.ts | 17 ++++- 3 files changed, 68 insertions(+), 30 deletions(-) diff --git a/apps/web/src/components/settings/ProjectDefaultsSettings.tsx b/apps/web/src/components/settings/ProjectDefaultsSettings.tsx index 5bbd3e05f0723..c37db7fd905ea 100644 --- a/apps/web/src/components/settings/ProjectDefaultsSettings.tsx +++ b/apps/web/src/components/settings/ProjectDefaultsSettings.tsx @@ -7,7 +7,7 @@ import { createModelSelection } from "@t3tools/shared/model"; import { resolveProjectSettings } from "@t3tools/shared/projectSettings"; import { useNavigate } from "@tanstack/react-router"; -import { mergeEnvironmentSettings } from "../../hooks/useSettings"; +import { useClientSettings } from "../../hooks/useSettings"; import { defaultModelSelectionsAreMixed, getCustomModelOptionsByInstance, @@ -52,6 +52,7 @@ function isWorktreeSubmodules(value: string | null): value is WorktreeSubmodules export function ProjectDefaultsSettings({ category }: { category: ProjectSettingsCategory }) { const { scope, target, targets, connectedEnvironments } = useSettingsScope(); const settings = useScopedSettings(); + const clientSettings = useClientSettings(); const updateSettings = useUpdateScopedSettings(); const navigate = useNavigate(); const { environments } = useEnvironments(); @@ -79,8 +80,9 @@ export function ProjectDefaultsSettings({ category }: { category: ProjectSetting ); const activeEntry = entries.find((entry) => entry.instanceId === selection?.instanceId); const mixedModel = defaultModelSelectionsAreMixed( + clientSettings, targets.map((candidate) => ({ - settings: mergeEnvironmentSettings(candidate.settings, settings), + settings: candidate.settings, providers: providersByEnvironment.get(candidate.environmentId) ?? EMPTY_SERVER_PROVIDERS, })), ); diff --git a/apps/web/src/modelSelection.test.ts b/apps/web/src/modelSelection.test.ts index ffaebf204d73b..e4043745790c0 100644 --- a/apps/web/src/modelSelection.test.ts +++ b/apps/web/src/modelSelection.test.ts @@ -1,11 +1,16 @@ import { ANTIGRAVITY_DEFAULT_MODEL, DEFAULT_TEXT_GENERATION_MODEL, + DEFAULT_SERVER_SETTINGS, ProviderDriverKind, ProviderInstanceId, type ServerProvider, } from "@t3tools/contracts"; -import { DEFAULT_UNIFIED_SETTINGS, type UnifiedSettings } from "@t3tools/contracts/settings"; +import { + DEFAULT_CLIENT_SETTINGS, + DEFAULT_UNIFIED_SETTINGS, + type UnifiedSettings, +} from "@t3tools/contracts/settings"; import { describe, expect, it } from "vite-plus/test"; import { createModelSelection } from "@t3tools/shared/model"; import { deriveEffectiveComposerModelState } from "./composerDraftStore"; @@ -92,34 +97,49 @@ describe("scoped default model selections", () => { ], }; - expect(defaultModelSelectionsAreMixed([codex, claude])).toBe(true); - expect(defaultModelSelectionsAreMixed([claude, codex])).toBe(true); - expect(defaultModelSelectionsAreMixed([codex, codex])).toBe(false); - expect(defaultModelSelectionsAreMixed([claude])).toBe(false); - expect(defaultModelSelectionsAreMixed([])).toBe(false); - expect(defaultModelSelectionsAreMixed([codex, { ...codex, providers: [] }])).toBe(true); + expect(defaultModelSelectionsAreMixed(DEFAULT_CLIENT_SETTINGS, [codex, claude])).toBe(true); + expect(defaultModelSelectionsAreMixed(DEFAULT_CLIENT_SETTINGS, [claude, codex])).toBe(true); + expect(defaultModelSelectionsAreMixed(DEFAULT_CLIENT_SETTINGS, [codex, codex])).toBe(false); + expect(defaultModelSelectionsAreMixed(DEFAULT_CLIENT_SETTINGS, [claude])).toBe(false); + expect(defaultModelSelectionsAreMixed(DEFAULT_CLIENT_SETTINGS, [])).toBe(false); + expect( + defaultModelSelectionsAreMixed(DEFAULT_CLIENT_SETTINGS, [codex, { ...codex, providers: [] }]), + ).toBe(true); expect( - defaultModelSelectionsAreMixed([ + defaultModelSelectionsAreMixed(DEFAULT_CLIENT_SETTINGS, [ codex, { ...codex, providers: [provider({ instanceId: "codex", models: ["gpt-6-sol"] })] }, ]), ).toBe(true); - expect( - defaultModelSelectionsAreMixed([ - codex, - { - ...codex, - settings: { - ...codex.settings, - providerModelPreferences: { - [ProviderInstanceId.make("codex")]: { - hiddenModels: ["gpt-6-astra"], - modelOrder: [], - }, - }, + const configured = { + settings: { + ...DEFAULT_SERVER_SETTINGS, + providerModelPreferences: { + [ProviderInstanceId.make("codex")]: { + hiddenModels: ["gpt-6-astra"], + modelOrder: [], }, - providers: [provider({ instanceId: "codex", models: ["gpt-6-astra", "gpt-6-sol"] })], }, + }, + providers: [provider({ instanceId: "codex", models: ["gpt-6-astra", "gpt-6-sol"] })], + }; + const inherited = { ...configured, settings: DEFAULT_SERVER_SETTINGS }; + expect(defaultModelSelectionsAreMixed(DEFAULT_CLIENT_SETTINGS, [configured, inherited])).toBe( + true, + ); + expect(defaultModelSelectionsAreMixed(DEFAULT_CLIENT_SETTINGS, [inherited, configured])).toBe( + true, + ); + const clientSettings = { + ...DEFAULT_CLIENT_SETTINGS, + providerModelPreferences: configured.settings.providerModelPreferences, + }; + expect(defaultModelSelectionsAreMixed(clientSettings, [configured, inherited])).toBe(false); + expect(defaultModelSelectionsAreMixed(clientSettings, [inherited, configured])).toBe(false); + expect( + defaultModelSelectionsAreMixed(clientSettings, [ + { ...inherited, settings: { ...DEFAULT_SERVER_SETTINGS, providerModelPreferences: {} } }, + inherited, ]), ).toBe(true); }); @@ -136,12 +156,17 @@ describe("scoped default model selections", () => { ), }, }; - expect(defaultModelSelectionsAreMixed([selected, { ...selected }])).toBe(false); expect( - defaultModelSelectionsAreMixed([selected, { providers, settings: DEFAULT_UNIFIED_SETTINGS }]), + defaultModelSelectionsAreMixed(DEFAULT_CLIENT_SETTINGS, [selected, { ...selected }]), + ).toBe(false); + expect( + defaultModelSelectionsAreMixed(DEFAULT_CLIENT_SETTINGS, [ + selected, + { providers, settings: DEFAULT_UNIFIED_SETTINGS }, + ]), ).toBe(true); expect( - defaultModelSelectionsAreMixed([ + defaultModelSelectionsAreMixed(DEFAULT_CLIENT_SETTINGS, [ selected, { ...selected, diff --git a/apps/web/src/modelSelection.ts b/apps/web/src/modelSelection.ts index 69ba546469027..6031154cfea93 100644 --- a/apps/web/src/modelSelection.ts +++ b/apps/web/src/modelSelection.ts @@ -7,6 +7,7 @@ import { ProviderDriverKind, ProviderInstanceId, type ServerProvider, + type ServerSettings, } from "@t3tools/contracts"; import { type CustomModelDefinition, @@ -16,7 +17,8 @@ import { resolveSelectableModel, } from "@t3tools/shared/model"; import { getComposerProviderState } from "./components/chat/composerProviderState"; -import { UnifiedSettings } from "@t3tools/contracts/settings"; +import { type ClientSettings, UnifiedSettings } from "@t3tools/contracts/settings"; +import { resolveModelPreferences } from "@t3tools/shared/serverSettings"; import * as Arr from "effect/Array"; import * as Equal from "effect/Equal"; import * as Result from "effect/Result"; @@ -311,8 +313,9 @@ export function resolveEffectiveDefaultModelSelection( } export function defaultModelSelectionsAreMixed( + clientSettings: ClientSettings, targets: ReadonlyArray<{ - settings: UnifiedSettings; + settings: ServerSettings; providers: ReadonlyArray; }>, ): boolean { @@ -322,7 +325,15 @@ export function defaultModelSelectionsAreMixed( return { stored, effective: selection - ? resolveEffectiveDefaultModelSelection(settings, providers, selection) + ? resolveEffectiveDefaultModelSelection( + { + ...clientSettings, + ...settings, + ...resolveModelPreferences(settings, clientSettings), + }, + providers, + selection, + ) : null, }; }); From de28d11843243f9080c9f73a0f5336fcf2ed756d Mon Sep 17 00:00:00 2001 From: James Tatum Date: Sat, 31 Oct 2026 00:06:40 -0700 Subject: [PATCH 3/3] fix(web): respect project providers in automatic defaults --- .../settings/ProjectDefaultsSettings.tsx | 10 +++--- apps/web/src/modelSelection.test.ts | 35 ++++++++++++++++++- apps/web/src/modelSelection.ts | 10 ++++-- 3 files changed, 48 insertions(+), 7 deletions(-) diff --git a/apps/web/src/components/settings/ProjectDefaultsSettings.tsx b/apps/web/src/components/settings/ProjectDefaultsSettings.tsx index c37db7fd905ea..b10af2fd1dda4 100644 --- a/apps/web/src/components/settings/ProjectDefaultsSettings.tsx +++ b/apps/web/src/components/settings/ProjectDefaultsSettings.tsx @@ -16,6 +16,7 @@ import { import { providerInstanceEntriesFor, resolveDefaultProviderModelSelection, + resolveProjectDefaultProviderModelSelection, } from "../../providerInstances"; import { useEnvironmentProviders, useEnvironments } from "../../state/environments"; import { EMPTY_SERVER_PROVIDERS } from "../../state/server"; @@ -64,10 +65,10 @@ export function ProjectDefaultsSettings({ category }: { category: ProjectSetting (representative?.serverConfig ? providersByEnvironment.get(representative.environmentId) : undefined) ?? EMPTY_SERVER_PROVIDERS; - const storedSelection = resolveDefaultProviderModelSelection( - providers, - settings.defaultModelSelection, - ); + const storedSelection = + settings.defaultModelSelection === null + ? resolveProjectDefaultProviderModelSelection(providers, settings, target?.projectId ?? null) + : resolveDefaultProviderModelSelection(providers, settings.defaultModelSelection); const selection = storedSelection ? resolveEffectiveDefaultModelSelection(settings, providers, storedSelection) : null; @@ -82,6 +83,7 @@ export function ProjectDefaultsSettings({ category }: { category: ProjectSetting const mixedModel = defaultModelSelectionsAreMixed( clientSettings, targets.map((candidate) => ({ + projectId: candidate.projectId, settings: candidate.settings, providers: providersByEnvironment.get(candidate.environmentId) ?? EMPTY_SERVER_PROVIDERS, })), diff --git a/apps/web/src/modelSelection.test.ts b/apps/web/src/modelSelection.test.ts index e4043745790c0..430188a7c99c0 100644 --- a/apps/web/src/modelSelection.test.ts +++ b/apps/web/src/modelSelection.test.ts @@ -2,6 +2,7 @@ import { ANTIGRAVITY_DEFAULT_MODEL, DEFAULT_TEXT_GENERATION_MODEL, DEFAULT_SERVER_SETTINGS, + ProjectId, ProviderDriverKind, ProviderInstanceId, type ServerProvider, @@ -83,10 +84,12 @@ describe("scoped default model selections", () => { // regression: #27997 equal automatic defaults can resolve to different providers it("compares automatic defaults using each environment's models", () => { const codex = { + projectId: null, settings: DEFAULT_UNIFIED_SETTINGS, providers: [provider({ instanceId: "codex", models: ["gpt-6-astra"] })], }; const claude = { + projectId: null, settings: DEFAULT_UNIFIED_SETTINGS, providers: [ provider({ @@ -111,7 +114,36 @@ describe("scoped default model selections", () => { { ...codex, providers: [provider({ instanceId: "codex", models: ["gpt-6-sol"] })] }, ]), ).toBe(true); + + const projectId = ProjectId.make("claude-only"); + const project = { + projectId, + settings: { + ...DEFAULT_SERVER_SETTINGS, + projectSettingsOverrides: { + [projectId]: { + providerInstances: { [ProviderInstanceId.make("codex")]: { enabled: false } }, + }, + }, + }, + providers: [...codex.providers, ...claude.providers], + }; + const remoteProject = { ...claude, projectId: ProjectId.make("remote-project") }; + expect(defaultModelSelectionsAreMixed(DEFAULT_CLIENT_SETTINGS, [project, remoteProject])).toBe( + false, + ); + expect(defaultModelSelectionsAreMixed(DEFAULT_CLIENT_SETTINGS, [remoteProject, project])).toBe( + false, + ); + expect( + defaultModelSelectionsAreMixed(DEFAULT_CLIENT_SETTINGS, [ + project, + { ...project, projectId: ProjectId.make("other-project") }, + ]), + ).toBe(true); + const configured = { + projectId: null, settings: { ...DEFAULT_SERVER_SETTINGS, providerModelPreferences: { @@ -147,6 +179,7 @@ describe("scoped default model selections", () => { it("keeps automatic, explicit, and different saved options distinct", () => { const providers = [provider({ instanceId: "codex", models: ["gpt-6-astra"] })]; const selected = { + projectId: null, providers, settings: { ...DEFAULT_UNIFIED_SETTINGS, @@ -162,7 +195,7 @@ describe("scoped default model selections", () => { expect( defaultModelSelectionsAreMixed(DEFAULT_CLIENT_SETTINGS, [ selected, - { providers, settings: DEFAULT_UNIFIED_SETTINGS }, + { projectId: null, providers, settings: DEFAULT_UNIFIED_SETTINGS }, ]), ).toBe(true); expect( diff --git a/apps/web/src/modelSelection.ts b/apps/web/src/modelSelection.ts index 6031154cfea93..c94de128de97e 100644 --- a/apps/web/src/modelSelection.ts +++ b/apps/web/src/modelSelection.ts @@ -4,6 +4,7 @@ import { DEFAULT_TEXT_GENERATION_MODEL_BY_PROVIDER, defaultInstanceIdForDriver, type ModelSelection, + type ProjectId, ProviderDriverKind, ProviderInstanceId, type ServerProvider, @@ -33,6 +34,7 @@ import { deriveProviderInstanceEntries, NO_PROVIDER_MODEL_SELECTION, resolveDefaultProviderModelSelection, + resolveProjectDefaultProviderModelSelection, } from "./providerInstances"; import { sortModelsForProviderInstance } from "./modelOrdering"; import { resolveModelShortName } from "@t3tools/client-runtime/provider-clients"; @@ -315,13 +317,17 @@ export function resolveEffectiveDefaultModelSelection( export function defaultModelSelectionsAreMixed( clientSettings: ClientSettings, targets: ReadonlyArray<{ + projectId: ProjectId | null; settings: ServerSettings; providers: ReadonlyArray; }>, ): boolean { - const selections = targets.map(({ settings, providers }) => { + const selections = targets.map(({ projectId, settings, providers }) => { const stored = settings.defaultModelSelection; - const selection = resolveDefaultProviderModelSelection(providers, stored); + const selection = + stored === null + ? resolveProjectDefaultProviderModelSelection(providers, settings, projectId) + : resolveDefaultProviderModelSelection(providers, stored); return { stored, effective: selection