fix(daemon): providers enable overrides review-disabled providers

Manual disable/enable now write to the SDK's manual lists instead of the
review-owned auto list, and display paths use the effective set
(review-disabled ∪ manual-disable − manual-enable):

- disable: add to manuallyDisabledProviders and clear any manual-enable
  override for that provider
- enable: clear the manual disable and record a manual-enable override so
  the Nostr review sync does not re-disable it
- /providers, cached models and per-model provider views: effective set
- refreshProvidersAndModels: mirror the review-disabled list exactly
  (null from the sync means "unchanged", so no clobbering with [])
This commit is contained in:
redshift
2026-08-17 09:01:09 +01:00
parent f43c85e4fb
commit 7f580a1e02
2 changed files with 75 additions and 25 deletions
+47 -14
View File
@@ -1048,8 +1048,16 @@ export function createDaemonRequestHandler(deps: {
const state = deps.store.getState();
const baseUrlsList: string[] = state.baseUrlsList || [];
const disabledProviders: string[] = [
...(state.disabledProviders || []),
// User-driven disables belong in the *manual* disabled list. The
// review-based list (setDisabledProviders) is owned by the Nostr
// kind-38425 review sync, which overwrites it and would otherwise
// silently re-enable manually-disabled providers. Disabling also
// clears any manual-enable override for that provider.
const manuallyDisabledProviders: string[] = [
...(state.manuallyDisabledProviders || []),
];
const manuallyEnabledProviders: string[] = [
...(state.manuallyEnabledProviders || []),
];
const toDisable: string[] = [];
@@ -1060,15 +1068,21 @@ export function createDaemonRequestHandler(deps: {
idx < baseUrlsList.length
) {
const baseUrl = baseUrlsList[idx]!;
if (!disabledProviders.includes(baseUrl)) {
disabledProviders.push(baseUrl);
if (!manuallyDisabledProviders.includes(baseUrl)) {
manuallyDisabledProviders.push(baseUrl);
toDisable.push(baseUrl);
}
const enabledPos = manuallyEnabledProviders.indexOf(baseUrl);
if (enabledPos !== -1) {
manuallyEnabledProviders.splice(enabledPos, 1);
}
}
}
deps.store.getState().setDisabledProviders(disabledProviders);
deps.discoveryAdapter.setDisabledProviders(disabledProviders);
deps.store.getState().setManuallyDisabledProviders(manuallyDisabledProviders);
deps.discoveryAdapter.setManuallyDisabledProviders(manuallyDisabledProviders);
deps.store.getState().setManuallyEnabledProviders(manuallyEnabledProviders);
deps.discoveryAdapter.setManuallyEnabledProviders(manuallyEnabledProviders);
res.writeHead(200, { "Content-Type": "application/json" });
res.end(
@@ -1104,8 +1118,14 @@ export function createDaemonRequestHandler(deps: {
const state = deps.store.getState();
const baseUrlsList: string[] = state.baseUrlsList || [];
const disabledProviders: string[] = [
...(state.disabledProviders || []),
// Re-enabling clears the manual disable and records a manual-enable
// override so the review sync (which disables providers without an
// lgtm review) does not silently re-disable it on the next pass.
const manuallyDisabledProviders: string[] = [
...(state.manuallyDisabledProviders || []),
];
const manuallyEnabledProviders: string[] = [
...(state.manuallyEnabledProviders || []),
];
const toEnable: string[] = [];
@@ -1116,16 +1136,21 @@ export function createDaemonRequestHandler(deps: {
idx < baseUrlsList.length
) {
const baseUrl = baseUrlsList[idx]!;
const pos = disabledProviders.indexOf(baseUrl);
const pos = manuallyDisabledProviders.indexOf(baseUrl);
if (pos !== -1) {
disabledProviders.splice(pos, 1);
toEnable.push(baseUrl);
manuallyDisabledProviders.splice(pos, 1);
}
if (!manuallyEnabledProviders.includes(baseUrl)) {
manuallyEnabledProviders.push(baseUrl);
}
toEnable.push(baseUrl);
}
}
deps.store.getState().setDisabledProviders(disabledProviders);
deps.discoveryAdapter.setDisabledProviders(disabledProviders);
deps.store.getState().setManuallyDisabledProviders(manuallyDisabledProviders);
deps.discoveryAdapter.setManuallyDisabledProviders(manuallyDisabledProviders);
deps.store.getState().setManuallyEnabledProviders(manuallyEnabledProviders);
deps.discoveryAdapter.setManuallyEnabledProviders(manuallyEnabledProviders);
res.writeHead(200, { "Content-Type": "application/json" });
res.end(
@@ -1344,7 +1369,15 @@ export function createDaemonRequestHandler(deps: {
const state = deps.store.getState();
const baseUrlsList: string[] = state.baseUrlsList || [];
const disabledProviders: string[] = state.disabledProviders || [];
const manuallyEnabled = new Set(
state.manuallyEnabledProviders || [],
);
const disabledProviders: string[] = [
...new Set([
...(state.disabledProviders || []),
...(state.manuallyDisabledProviders || []),
]),
].filter((url) => !manuallyEnabled.has(url));
const providers = baseUrlsList.map((baseUrl, index) => ({
index,
+28 -11
View File
@@ -41,8 +41,17 @@ export function createModelService(
const currentProviders = new Set(
modelManager.getBaseUrls().map(normalizeBaseUrl),
);
const s = store.getState();
const manuallyEnabled = new Set(
(s.manuallyEnabledProviders || []).map(normalizeBaseUrl),
);
const disabledProviders = new Set(
(store.getState().disabledProviders || []).map(normalizeBaseUrl),
[
...(s.disabledProviders || []),
...(s.manuallyDisabledProviders || []),
]
.map(normalizeBaseUrl)
.filter((url) => !manuallyEnabled.has(url)),
);
const bestById = new Map<string, PricedModel>();
@@ -140,7 +149,16 @@ export function createModelService(
): Promise<ModelWithProviders | null> => {
await ensureProvidersBootstrapped();
const disabledSet = new Set<string>(store.getState().disabledProviders || []);
const s = store.getState();
const manuallyEnabled = new Set<string>(
(s.manuallyEnabledProviders || []).map(normalizeBaseUrl),
);
const disabledSet = new Set<string>(
[
...(s.disabledProviders || []),
...(s.manuallyDisabledProviders || []),
].filter((url) => !manuallyEnabled.has(normalizeBaseUrl(url))),
);
// Use the SDK ranking (sorted by prompt+completion per million tokens)
// so the display order matches real routing. includeDisabled keeps
@@ -212,28 +230,27 @@ export function createModelService(
undefined,
true,
);
if (reviewedDisabled.length > 0) {
if (reviewedDisabled && reviewedDisabled.length > 0) {
console.log(
`Review sync disabled ${reviewedDisabled.length} provider(s): ${reviewedDisabled.join(", ")}`,
);
}
// Sync discovered providers into the store
const { baseUrlsList, setBaseUrlsList, disabledProviders, setDisabledProviders } =
store.getState() as any;
const { setBaseUrlsList, setDisabledProviders } = store.getState() as any;
// Replace baseUrlsList with the fresh provider list
setBaseUrlsList(providers);
// Merge review-disabled providers into the store's disabled list
const existingDisabled = new Set(disabledProviders || []);
for (const url of reviewedDisabled) {
existingDisabled.add(url);
// Mirror the review-disabled set into the store's auto-disabled list.
// `null` means the review sync left the adapter unchanged (e.g. no lgtm
// reviews found), so we must not clobber the store list with an empty array.
if (reviewedDisabled !== null) {
setDisabledProviders(reviewedDisabled);
}
setDisabledProviders([...existingDisabled]);
console.log(
`Provider refresh complete: ${providers.length} total, ${existingDisabled.size} disabled`,
`Provider refresh complete: ${providers.length} total, ${reviewedDisabled?.length ?? store.getState().disabledProviders?.length ?? 0} review-disabled`,
);
};