mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-31 04:12:10 +03:00
* fix(models): preserve eye-hidden models across auto-sync (#3782) * chore(quality): re-baseline file-size for models.ts growth (#3782)
This commit is contained in:
committed by
GitHub
parent
e952ae1406
commit
ef324cd00e
@@ -9,6 +9,7 @@
|
||||
### 🐛 Fixed
|
||||
|
||||
- **fix(intelligence): run pricing + models.dev sync from the live startup path** — like the Arena ELO sync (v3.8.24), the external **pricing sync** (`PRICING_SYNC_ENABLED`) and the **models.dev capability sync** (Settings → AI toggle) were only initialized from `server-init.ts`, which the Next standalone runtime never executes — and models.dev had no caller at all. Their toggles were inert in production. Both are now initialized from `instrumentation-node.ts` (self-gated, opt-in preserved, non-blocking, never fatal). (thanks @diegosouzapw)
|
||||
- **fix(models): preserve eye-hidden models across auto-sync / import** — hiding models via the visibility (eye) toggle to keep only a combo's models was undone on every model import or auto-sync, which re-showed all of them. The sync re-import treated "hidden" identically to "deleted" and dropped both; a distinct `isDeleted` marker now separates the trash/delete path (still dropped on re-import, #3199) from the eye toggle (preserved as listed-but-hidden), and eye-hidden models are no longer re-aliased into the routable catalog on sync. ([#3782](https://github.com/diegosouzapw/OmniRoute/issues/3782) — thanks @xenstar)
|
||||
- **fix(providers): correct the lmarena cookie hint (`session` → `arena-auth-prod-v1`)** — the lmarena credential hint asked for a cookie named `session`, but lmarena.ai's real auth cookie is `arena-auth-prod-v1`, so users who pasted only `session=…` hit validation failures. The credential name, placeholder and storage keys now use the correct name (the legacy `session` key is retained for back-compat with already-saved credentials). ([#3810](https://github.com/diegosouzapw/OmniRoute/issues/3810) — thanks @xspylol)
|
||||
|
||||
---
|
||||
|
||||
@@ -79,7 +79,7 @@
|
||||
"src/lib/db/apiKeys.ts": 1633,
|
||||
"src/lib/db/core.ts": 1820,
|
||||
"src/lib/db/migrationRunner.ts": 1100,
|
||||
"src/lib/db/models.ts": 1132,
|
||||
"src/lib/db/models.ts": 1180,
|
||||
"src/lib/db/providers.ts": 1050,
|
||||
"src/lib/db/proxies.ts": 1048,
|
||||
"src/lib/db/settings.ts": 1149,
|
||||
@@ -112,5 +112,6 @@
|
||||
"_rebaseline_2026_06_12_phase1t": "Phase 1t (#3501): ProviderDetailPageClient.tsx 1377→782 — META ≤800 ATINGIDA (extraídos ProviderPageHeader, CompatibleNodeCard, ProviderModalsPanel, EmptyConnectionsPlaceholder, UpstreamProxyCard, SearchProviderCard + hooks useConnectionGate/useProviderNodeActions). Drift concorrente reconciliado: ResilienceTab/sse-chat/sse-auth/accountFallback/combo (merges #3629 model-lockout etc.).",
|
||||
"_rebaseline_2026_06_12_v3823_new_features": "Re-baseline v3.8.23 pós-merge de #3742 (cost drilldown: ApiManagerPageClient.tsx +21, CostOverviewTab.tsx +14, providerLimits.ts +2, usage.ts +53) + #3743 (provider display modes: ProviderDetailPageClient.tsx +2, providerPageHelpers.ts +42, providers.ts +2) + #3740 (semantic cache key isolation: chat.ts +3). Crescimento justificado por features novas mergeadas no ciclo.",
|
||||
"_rebaseline_2026_06_13_combo_quota_audit": "Re-baseline consciente do audit combo+quota (PR #3779): combo.ts 5054→5131 (+77). Crescimento = 5 fixes TDD + estratégia complexity-aware 2026 (W1 clampComboDepth + threading de maxDepth em 6 assinaturas/dispatch/DAG; W2 extração shouldSkipForPredictedTtft; W4 scoreAutoTargets exportado + param manifestHint). A parte limpa-extraível do W4 (construção do hint inline, ~30 linhas) FOI extraída para autoCombo/complexityRouter.ts (buildComplexityRoutingHint) — este +76 é o resíduo irredutível (edição de assinaturas/threading, não bloco movível). Shrink estrutural de combo.ts segue com #3501.",
|
||||
"_rebaseline_2026_06_13_v3824_3776": "Re-baseline v3.8.24 pós #3776 (strict-mode controls Claude Code default models: ApiManagerPageClient.tsx 2701→2909 = UI de famílias bloqueáveis cc/* + chips; apiKeys.ts 1490→1633 = blocked_models deny-list + candidatos de permissão claude-code; schemas.ts 2515→2519 = reformatação Prettier + reasoningTokenBufferEnabled restaurado) + carry-over base.ts 1205→1218 do #3780 (enforceThinkingTemperature no chokepoint, drift de baseline não bumpado no merge). Crescimento de feature; sem god-component novo."
|
||||
"_rebaseline_2026_06_13_v3824_3776": "Re-baseline v3.8.24 pós #3776 (strict-mode controls Claude Code default models: ApiManagerPageClient.tsx 2701→2909 = UI de famílias bloqueáveis cc/* + chips; apiKeys.ts 1490→1633 = blocked_models deny-list + candidatos de permissão claude-code; schemas.ts 2515→2519 = reformatação Prettier + reasoningTokenBufferEnabled restaurado) + carry-over base.ts 1205→1218 do #3780 (enforceThinkingTemperature no chokepoint, drift de baseline não bumpado no merge). Crescimento de feature; sem god-component novo.",
|
||||
"_rebaseline_2026_06_13_3782_hide_persist": "Re-baseline #3782 (preservar modelos eye-hidden no auto-sync): models.ts 1132→1180 (+48). Crescimento = flag distinto isDeleted em ModelCompatOverride/ModelCompatPatch + handling em mergeModelCompatOverride + helper getModelIsDeleted, para separar 'deletado' (trash, dropado no re-sync #3199) de 'oculto' (eye toggle, preservado). Lógica coesa de visibilidade no módulo db; não-extraível. Os 3 drifts release-wide (ProxyRegistryManager/sidebarVisibility/schemas) são do #3809 do owner, não deste PR."
|
||||
}
|
||||
|
||||
@@ -400,10 +400,13 @@ export async function DELETE(request) {
|
||||
const removedCustom = await removeCustomModel(provider, modelId);
|
||||
const removedSynced = await removeSyncedAvailableModel(provider, modelId);
|
||||
if (removedSynced) {
|
||||
// #3199: mark the deleted synced model hidden so a later auto-fetch
|
||||
// re-import (replaceSyncedAvailableModelsForConnection) does not re-add it
|
||||
// — otherwise the model reappears and the delete looks like it did nothing.
|
||||
mergeModelCompatOverride(provider, modelId, { isHidden: true });
|
||||
// #3199 + #3782: mark the deleted synced model with the DISTINCT `isDeleted`
|
||||
// marker so a later auto-fetch re-import does not re-add it. We also keep
|
||||
// `isHidden:true` so existing UI/visibility behavior is unchanged. The sync
|
||||
// filter keys on `isDeleted` (not `isHidden`), which is what lets an
|
||||
// eye/visibility-hidden model (`isHidden` only) survive a re-sync while a
|
||||
// deleted one stays dropped.
|
||||
mergeModelCompatOverride(provider, modelId, { isDeleted: true, isHidden: true });
|
||||
}
|
||||
const removed = removedCustom || removedSynced;
|
||||
const removedAliases = await deleteManagedAvailableModelAliases(provider, [modelId]);
|
||||
|
||||
@@ -111,6 +111,16 @@ export type ModelCompatOverride = {
|
||||
compatByProtocol?: CompatByProtocolMap;
|
||||
upstreamHeaders?: Record<string, string>;
|
||||
isHidden?: boolean;
|
||||
/**
|
||||
* #3782 — distinct "deleted" marker, separate from {@link isHidden}.
|
||||
*
|
||||
* `isHidden` is set by the EYE/visibility toggle and must be PRESERVED across a
|
||||
* re-sync (the model stays listed-but-hidden). `isDeleted` is set by the trash/
|
||||
* DELETE route and means "drop this id on every re-import" (#3199). Keeping the
|
||||
* two flags distinct is what lets {@link replaceSyncedAvailableModelsForConnection}
|
||||
* preserve eye-hidden models while still dropping deleted ones.
|
||||
*/
|
||||
isDeleted?: boolean;
|
||||
};
|
||||
|
||||
function readCompatList(providerId: string): ModelCompatOverride[] {
|
||||
@@ -156,6 +166,8 @@ export type ModelCompatPatch = {
|
||||
/** Replace top-level extra headers for override-only rows; omit to leave unchanged. */
|
||||
upstreamHeaders?: Record<string, string> | null;
|
||||
isHidden?: boolean | null;
|
||||
/** #3782 — distinct delete marker; set by the DELETE route, never by the eye toggle. */
|
||||
isDeleted?: boolean | null;
|
||||
};
|
||||
|
||||
function compatByProtocolHasEntries(map: CompatByProtocolMap | undefined): boolean {
|
||||
@@ -210,11 +222,20 @@ export function mergeModelCompatOverride(
|
||||
next.isHidden = Boolean(patch.isHidden);
|
||||
}
|
||||
}
|
||||
if ("isDeleted" in patch) {
|
||||
if (patch.isDeleted === null || patch.isDeleted === false) {
|
||||
delete next.isDeleted;
|
||||
} else {
|
||||
next.isDeleted = Boolean(patch.isDeleted);
|
||||
}
|
||||
}
|
||||
const hasHiddenFlag = Object.prototype.hasOwnProperty.call(next, "isHidden");
|
||||
const hasDeletedFlag = Object.prototype.hasOwnProperty.call(next, "isDeleted");
|
||||
if (
|
||||
next.normalizeToolCallId ||
|
||||
hasPreserveFlag ||
|
||||
hasHiddenFlag ||
|
||||
hasDeletedFlag ||
|
||||
compatByProtocolHasEntries(next.compatByProtocol) ||
|
||||
hasTopUpstream
|
||||
) {
|
||||
@@ -714,10 +735,15 @@ export async function replaceSyncedAvailableModelsForConnection(
|
||||
): Promise<SyncedAvailableModel[]> {
|
||||
const db = getDbInstance();
|
||||
const key = `${providerId}:${connectionId}`;
|
||||
// #3199: drop ids the operator has deleted/hidden so a re-fetch does not
|
||||
// re-import a model that was explicitly removed.
|
||||
// #3199: drop ids the operator DELETED (trash) so a re-fetch does not re-import
|
||||
// a model that was explicitly removed.
|
||||
// #3782: key ONLY on the distinct `isDeleted` marker — NOT on `isHidden`.
|
||||
// Eye/visibility-hidden models (`isHidden:true`, no `isDeleted`) must stay in
|
||||
// the synced store so they remain listed-but-hidden across re-syncs instead of
|
||||
// churning back on through the managed-alias path ("Auto Sync Enabling all
|
||||
// Models"). See getModelIsDeleted for the legacy-row caveat.
|
||||
const normalizedModels = normalizeSyncedAvailableModels(models).filter(
|
||||
(m) => !getModelIsHidden(providerId, m.id)
|
||||
(m) => !getModelIsDeleted(providerId, m.id)
|
||||
);
|
||||
if (normalizedModels.length === 0) {
|
||||
db.prepare("DELETE FROM key_value WHERE namespace = 'syncedAvailableModels' AND key = ?").run(
|
||||
@@ -1034,6 +1060,28 @@ export function getModelIsHidden(providerId: string, modelId: string): boolean {
|
||||
return Boolean(co?.isHidden);
|
||||
}
|
||||
|
||||
/**
|
||||
* #3782 — Check if a model was DELETED (trash) rather than merely eye-hidden.
|
||||
*
|
||||
* Only the DELETE route sets `isDeleted`. The sync re-import filter keys on this
|
||||
* (not on `isHidden`) so eye-hidden models survive a re-sync while deleted ones
|
||||
* stay dropped.
|
||||
*
|
||||
* Legacy caveat: rows written by the DELETE route BEFORE this change carry only
|
||||
* `isHidden:true` (no `isDeleted`). Treating bare legacy `isHidden:true` as
|
||||
* deleted here would resurrect the #3782 bug for eye-hidden models; treating it
|
||||
* as "kept" would resurrect previously-deleted models. Resurrecting a deleted
|
||||
* model is the less-surprising, recoverable outcome (the operator can re-hide or
|
||||
* re-delete it), whereas silently dropping an eye-hidden model is the reported
|
||||
* regression — so we deliberately key ONLY on the explicit `isDeleted` flag and
|
||||
* accept that a handful of pre-existing deleted rows may reappear once after the
|
||||
* upgrade. Going forward both paths write the correct distinct markers.
|
||||
*/
|
||||
export function getModelIsDeleted(providerId: string, modelId: string): boolean {
|
||||
const co = readCompatList(providerId).find((e) => e.id === modelId);
|
||||
return Boolean(co?.isDeleted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Persist the hidden flag for a model. Stores the override on the custom-model
|
||||
* row when one exists, otherwise on the compat-override list. Setting
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
getCustomModels,
|
||||
getModelIsHidden,
|
||||
getSyncedAvailableModelsForConnection,
|
||||
mergeModelCompatOverride,
|
||||
replaceCustomModels,
|
||||
@@ -271,7 +272,10 @@ export async function importManagedModels({
|
||||
current = (ANTIGRAVITY_MODEL_ALIASES as any)[current];
|
||||
continue;
|
||||
}
|
||||
if (ANTIGRAVITY_REVERSE_MODEL_ALIASES && (ANTIGRAVITY_REVERSE_MODEL_ALIASES as any)[current]) {
|
||||
if (
|
||||
ANTIGRAVITY_REVERSE_MODEL_ALIASES &&
|
||||
(ANTIGRAVITY_REVERSE_MODEL_ALIASES as any)[current]
|
||||
) {
|
||||
current = (ANTIGRAVITY_REVERSE_MODEL_ALIASES as any)[current];
|
||||
continue;
|
||||
}
|
||||
@@ -306,13 +310,17 @@ export async function importManagedModels({
|
||||
await setMitmAliasAll("antigravity", mappings);
|
||||
}
|
||||
|
||||
|
||||
let syncedAliases = 0;
|
||||
if (usesManagedAvailableModels(providerId) && (mode === "merge" || discoveredModels.length > 0)) {
|
||||
const aliasModelIds = mode === "sync" ? syncedAvailableModels : discoveredModels;
|
||||
// #3782: eye-hidden models now survive in `syncedAvailableModels` (so they stay
|
||||
// listed-but-hidden), but they must NOT be re-assigned a routable managed alias
|
||||
// — otherwise auto-sync silently re-enables routing for a model the operator hid.
|
||||
// Exclude `isHidden` ids from the alias assignment. `pruneMissing` (sync mode)
|
||||
// then drops any stale alias an eye-hidden model previously held.
|
||||
const aliasSync = await syncManagedAvailableModelAliases(
|
||||
providerId,
|
||||
aliasModelIds.map((model) => model.id),
|
||||
aliasModelIds.map((model) => model.id).filter((id) => !getModelIsHidden(providerId, id)),
|
||||
{ pruneMissing: mode === "sync" }
|
||||
);
|
||||
syncedAliases = aliasSync.assignedAliases.length;
|
||||
|
||||
@@ -3,9 +3,14 @@
|
||||
* across an auto-fetch re-import.
|
||||
*
|
||||
* #3204 added `removeSyncedAvailableModel`, but the DELETE route did not mark the
|
||||
* model hidden and `replaceSyncedAvailableModelsForConnection` did not skip hidden
|
||||
* ids — so the next `/models` sync re-imported the model and it reappeared. This
|
||||
* test guards that a hidden id is filtered out on re-import.
|
||||
* model deleted and `replaceSyncedAvailableModelsForConnection` did not skip
|
||||
* deleted ids — so the next `/models` sync re-imported the model and it
|
||||
* reappeared. This test guards that a deleted id is filtered out on re-import.
|
||||
*
|
||||
* #3782 update: the delete marker is now the DISTINCT `isDeleted` flag (the route
|
||||
* sets `isDeleted` + `isHidden`), separate from the EYE/visibility toggle which
|
||||
* sets `isHidden` only and must be preserved across re-syncs. The sync filter
|
||||
* keys on `isDeleted`, so this test now simulates the route via `isDeleted`.
|
||||
*/
|
||||
import test, { before, after } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
@@ -22,8 +27,11 @@ import fs from "node:fs";
|
||||
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-test-synced-del-"));
|
||||
process.env.DATA_DIR = tmpDir;
|
||||
|
||||
const { replaceSyncedAvailableModelsForConnection, getSyncedAvailableModels, mergeModelCompatOverride } =
|
||||
await import("../../src/lib/localDb.ts");
|
||||
const {
|
||||
replaceSyncedAvailableModelsForConnection,
|
||||
getSyncedAvailableModels,
|
||||
mergeModelCompatOverride,
|
||||
} = await import("../../src/lib/localDb.ts");
|
||||
const { resetDbInstance } = await import("../../src/lib/db/core.ts");
|
||||
|
||||
before(() => {
|
||||
@@ -47,8 +55,12 @@ test("a hidden (deleted) synced model is not re-added on re-import", async () =>
|
||||
let synced = (await getSyncedAvailableModels(provider)).map((m) => m.id);
|
||||
assert.ok(synced.includes("model-del"), "both models present after first sync");
|
||||
|
||||
// Operator deletes model-del → the route marks it hidden (#3199).
|
||||
mergeModelCompatOverride(provider, "model-del", { isHidden: true });
|
||||
// Operator deletes model-del → the DELETE route marks it deleted.
|
||||
// #3782: the route now writes the DISTINCT `isDeleted` marker (plus `isHidden`
|
||||
// for back-compat) instead of bare `isHidden`, so an eye/visibility-hidden
|
||||
// model — which sets `isHidden` only — is preserved across a re-sync while a
|
||||
// genuinely-deleted one stays dropped. The sync filter keys on `isDeleted`.
|
||||
mergeModelCompatOverride(provider, "model-del", { isDeleted: true, isHidden: true });
|
||||
|
||||
// Auto-fetch re-imports the SAME upstream list (still advertising model-del).
|
||||
await replaceSyncedAvailableModelsForConnection(provider, connectionId, [
|
||||
|
||||
132
tests/unit/synced-model-hide-persist-3782.test.ts
Normal file
132
tests/unit/synced-model-hide-persist-3782.test.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
/**
|
||||
* #3782 — "Auto Sync Enabling all Models".
|
||||
*
|
||||
* The user hides models with the EYE/visibility toggle (writes `isHidden:true`
|
||||
* via `mergeModelCompatOverride`) to keep only their combo's models. Before this
|
||||
* fix, `replaceSyncedAvailableModelsForConnection` dropped EVERY hidden id on a
|
||||
* re-sync (it could not tell an eye-hidden model from a DELETED one), so the
|
||||
* model fell out of the synced store and then churned back through the managed
|
||||
* alias path — the reported "all models turn back on".
|
||||
*
|
||||
* The fix separates the two signals:
|
||||
* - DELETE (trash) marks `isDeleted:true` (+ keeps `isHidden:true` for back-compat).
|
||||
* - The EYE toggle sets only `isHidden:true`.
|
||||
* - The sync filter drops a model only when it is DELETED, so eye-hidden models
|
||||
* stay listed-but-hidden across re-syncs.
|
||||
*
|
||||
* This test guards that an eye-hidden model survives re-import (Test A), that a
|
||||
* genuinely-new model defaults to visible (Test B), and that the DELETE path
|
||||
* still drops on re-import (Test C — mirrors the #3199 delete flow).
|
||||
*/
|
||||
import test, { before, after } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import fs from "node:fs";
|
||||
|
||||
// Hermetic DB: this test writes overrides into the `modelCompatOverrides`
|
||||
// key_value namespace. Without an isolated DATA_DIR it would leak that state
|
||||
// into the shared dev/CI database and never clean it up, so the SECOND run
|
||||
// would see stale overrides and the first-sync precondition would fail. Point
|
||||
// DATA_DIR at a throwaway dir before any import that opens the SQLite handle.
|
||||
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-test-synced-hide-"));
|
||||
process.env.DATA_DIR = tmpDir;
|
||||
|
||||
const {
|
||||
replaceSyncedAvailableModelsForConnection,
|
||||
getSyncedAvailableModels,
|
||||
mergeModelCompatOverride,
|
||||
getModelIsHidden,
|
||||
} = await import("../../src/lib/localDb.ts");
|
||||
const { resetDbInstance } = await import("../../src/lib/db/core.ts");
|
||||
|
||||
before(() => {
|
||||
resetDbInstance();
|
||||
});
|
||||
|
||||
after(() => {
|
||||
// Release the SQLite handle so the Node test runner can exit, then remove the
|
||||
// throwaway DATA_DIR (CLAUDE.md "Database Handles in Tests").
|
||||
resetDbInstance();
|
||||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
const PROVIDER = "llama-cpp";
|
||||
const CONNECTION = "conn-3782";
|
||||
|
||||
test("A: an EYE-hidden synced model is preserved (listed-but-hidden) across re-import", async () => {
|
||||
// Initial sync brings in three models, all visible.
|
||||
await replaceSyncedAvailableModelsForConnection(PROVIDER, CONNECTION, [
|
||||
{ id: "A", name: "Alpha" },
|
||||
{ id: "B", name: "Bravo" },
|
||||
{ id: "C", name: "Charlie" },
|
||||
]);
|
||||
let synced = (await getSyncedAvailableModels(PROVIDER)).map((m) => m.id);
|
||||
assert.deepEqual(synced.sort(), ["A", "B", "C"], "all three present after first sync");
|
||||
assert.equal(getModelIsHidden(PROVIDER, "B"), false, "B is visible before eye-hide");
|
||||
|
||||
// Operator hides B with the EYE toggle (visibility only, NOT a delete).
|
||||
mergeModelCompatOverride(PROVIDER, "B", { isHidden: true });
|
||||
assert.equal(getModelIsHidden(PROVIDER, "B"), true, "B is eye-hidden");
|
||||
|
||||
// Auto-fetch re-imports the SAME upstream list (still advertising B).
|
||||
await replaceSyncedAvailableModelsForConnection(PROVIDER, CONNECTION, [
|
||||
{ id: "A", name: "Alpha" },
|
||||
{ id: "B", name: "Bravo" },
|
||||
{ id: "C", name: "Charlie" },
|
||||
]);
|
||||
|
||||
synced = (await getSyncedAvailableModels(PROVIDER)).map((m) => m.id);
|
||||
assert.ok(synced.includes("B"), "#3782: eye-hidden B STAYS in the synced list after re-sync");
|
||||
assert.equal(
|
||||
getModelIsHidden(PROVIDER, "B"),
|
||||
true,
|
||||
"#3782: eye-hidden B is NOT re-enabled (stays hidden) after re-sync"
|
||||
);
|
||||
assert.equal(getModelIsHidden(PROVIDER, "A"), false, "A stays visible");
|
||||
assert.equal(getModelIsHidden(PROVIDER, "C"), false, "C stays visible");
|
||||
assert.ok(synced.includes("A") && synced.includes("C"), "A and C still present");
|
||||
});
|
||||
|
||||
test("B: a genuinely-new model on re-sync defaults to VISIBLE", async () => {
|
||||
// Continue from Test A state (B eye-hidden); add a new model D.
|
||||
await replaceSyncedAvailableModelsForConnection(PROVIDER, CONNECTION, [
|
||||
{ id: "A", name: "Alpha" },
|
||||
{ id: "B", name: "Bravo" },
|
||||
{ id: "C", name: "Charlie" },
|
||||
{ id: "D", name: "Delta" },
|
||||
]);
|
||||
|
||||
const synced = (await getSyncedAvailableModels(PROVIDER)).map((m) => m.id);
|
||||
assert.ok(synced.includes("D"), "new model D is imported");
|
||||
assert.equal(getModelIsHidden(PROVIDER, "D"), false, "new model D defaults to visible");
|
||||
// Eye-hidden B is still preserved + hidden.
|
||||
assert.ok(synced.includes("B"), "eye-hidden B still present");
|
||||
assert.equal(getModelIsHidden(PROVIDER, "B"), true, "eye-hidden B still hidden");
|
||||
});
|
||||
|
||||
test("C: a DELETED synced model still stays out on re-import (delete signal)", async () => {
|
||||
const provider = "llama-cpp-del";
|
||||
const connection = "conn-3782-del";
|
||||
|
||||
await replaceSyncedAvailableModelsForConnection(provider, connection, [
|
||||
{ id: "keep", name: "Keep" },
|
||||
{ id: "del", name: "Delete me" },
|
||||
]);
|
||||
let synced = (await getSyncedAvailableModels(provider)).map((m) => m.id);
|
||||
assert.ok(synced.includes("del"), "both present after first sync");
|
||||
|
||||
// Operator DELETES (trash) `del` → the route marks it deleted. Mirror the real
|
||||
// DELETE route: it sets BOTH the distinct delete marker and (back-compat) hidden.
|
||||
mergeModelCompatOverride(provider, "del", { isDeleted: true, isHidden: true });
|
||||
|
||||
// Auto-fetch re-imports the SAME upstream list (still advertising `del`).
|
||||
await replaceSyncedAvailableModelsForConnection(provider, connection, [
|
||||
{ id: "keep", name: "Keep" },
|
||||
{ id: "del", name: "Delete me" },
|
||||
]);
|
||||
|
||||
synced = (await getSyncedAvailableModels(provider)).map((m) => m.id);
|
||||
assert.ok(synced.includes("keep"), "non-deleted model stays");
|
||||
assert.ok(!synced.includes("del"), "DELETED model must NOT be re-added by the re-import");
|
||||
});
|
||||
Reference in New Issue
Block a user