feat(cli): warn when workspace overrides global modelProviders (#3148)
Some checks are pending
Qwen Code CI / Lint (push) Waiting to run
Qwen Code CI / Test (push) Blocked by required conditions
Qwen Code CI / Test-1 (push) Blocked by required conditions
Qwen Code CI / Test-2 (push) Blocked by required conditions
Qwen Code CI / Test-3 (push) Blocked by required conditions
Qwen Code CI / Test-4 (push) Blocked by required conditions
Qwen Code CI / Test-5 (push) Blocked by required conditions
Qwen Code CI / Test-7 (push) Blocked by required conditions
Qwen Code CI / Test-6 (push) Blocked by required conditions
Qwen Code CI / Test-8 (push) Blocked by required conditions
Qwen Code CI / Post Coverage Comment (push) Blocked by required conditions
Qwen Code CI / CodeQL (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run

* feat(cli): warn when workspace overrides user modelProviders

* fix(cli): align modelProviders warning with current merge behavior

---------

Co-authored-by: jinye.djy <jinye.djy@alibaba-inc.com>
This commit is contained in:
jinye 2026-04-13 10:43:16 +08:00 committed by GitHub
parent 1557d93043
commit 732cee2604
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 151 additions and 1 deletions

View file

@ -20,6 +20,7 @@ import stripJsonComments from 'strip-json-comments';
import { DefaultLight } from '../ui/themes/default-light.js';
import { DefaultDark } from '../ui/themes/default.js';
import { isWorkspaceTrusted } from './trustedFolders.js';
import { hasOwnModelProviders } from './modelProvidersScope.js';
import {
type Settings,
type MemoryImportFormat,
@ -249,6 +250,57 @@ function getSettingsFileKeyWarnings(
return warnings;
}
function isPlainObject(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null && !Array.isArray(value);
}
function hasAnyProviderEntries(modelProviders: unknown): boolean {
if (!isPlainObject(modelProviders)) {
return false;
}
return Object.values(modelProviders).some(
(providerModels) => Array.isArray(providerModels) && providerModels.length > 0,
);
}
function getModelProvidersOverrideWarnings(
loadedSettings: LoadedSettings,
): string[] {
// Untrusted workspaces are ignored in merge, so they cannot shadow user modelProviders.
if (!loadedSettings.isTrusted) {
return [];
}
const userOriginal =
loadedSettings.user.originalSettings as unknown as Record<string, unknown>;
const workspaceOriginal =
loadedSettings.workspace.originalSettings as unknown as Record<
string,
unknown
>;
if (!hasOwnModelProviders(userOriginal) || !hasOwnModelProviders(workspaceOriginal)) {
return [];
}
const userModelProviders = userOriginal['modelProviders'];
const workspaceModelProviders = workspaceOriginal['modelProviders'];
const workspaceIsEmptyModelProviders =
isPlainObject(workspaceModelProviders) &&
Object.keys(workspaceModelProviders).length === 0;
if (!workspaceIsEmptyModelProviders || !hasAnyProviderEntries(userModelProviders)) {
return [];
}
return [
`Warning: '${loadedSettings.workspace.path}' defines an empty 'modelProviders' object. ` +
`This has no effect with current merge behavior, but may indicate a configuration error. ` +
`If REPLACE semantics are introduced for 'modelProviders' in the future, this would override user-level model providers in '${loadedSettings.user.path}'.`,
];
}
/**
* Collects warnings for ignored legacy and unknown settings keys,
* as well as migration warnings.
@ -283,6 +335,10 @@ export function getSettingsWarnings(loadedSettings: LoadedSettings): string[] {
}
}
for (const warning of getModelProvidersOverrideWarnings(loadedSettings)) {
warningSet.add(warning);
}
return [...warningSet];
}