mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-06 23:32:12 +03:00
* feat(providers): add GPT-5.6 model family * fix(chatgpt-web): resume temporary chat handoffs * fix(codex): auto-merge discovery, filter denylist, revalidate on lifecycle Restore live/GitHub auto-merge for Codex catalogs, drop models via explicit denylist (GPT-5.4 family), and run scrub+live re-sync once on first-start, app upgrade, or setup completion. Success log: kill deprecated models complete. * fix(codex): preserve live catalog reconciliation Expose remote-only Codex models without dropping user custom entries, and complete lifecycle revalidation only after a successful internal sync. Keep credentialed self-fetches pinned to the active dashboard listener. --------- Co-authored-by: backryun <backryun@daonlab.local>
258 lines
8.0 KiB
TypeScript
258 lines
8.0 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import fs from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import test from "node:test";
|
|
|
|
import type { SyncedAvailableModel } from "../../src/lib/db/models.ts";
|
|
import {
|
|
createCodexCatalogRevalidationCoordinator,
|
|
executeCodexCatalogRevalidation,
|
|
resolveBootRevalidationReason,
|
|
resolveCodexCatalogAppVersion,
|
|
scrubSyncedModelsWithCodexDenylist,
|
|
} from "../../src/shared/services/codexCatalogRevalidation.ts";
|
|
|
|
test("Codex revalidation avoids top-level createRequire in packaged Next modules", () => {
|
|
const source = fs.readFileSync(
|
|
path.join(process.cwd(), "src/shared/services/codexCatalogRevalidation.ts"),
|
|
"utf8"
|
|
);
|
|
assert.doesNotMatch(source, /^const\s+\w+\s*=\s*createRequire\s*\(/m);
|
|
});
|
|
|
|
test("scrubSyncedModelsWithCodexDenylist drops the GPT-5.4 family and keeps others", () => {
|
|
const input = [
|
|
{ id: "gpt-5.6-sol", name: "Sol", source: "imported" },
|
|
{ id: "gpt-5.4", name: "Retired", source: "imported" },
|
|
{ id: "gpt-5.4-mini", name: "Retired Mini", source: "imported" },
|
|
{ id: "future-codex-experimental", name: "Future", source: "imported" },
|
|
] satisfies SyncedAvailableModel[];
|
|
const { kept, removedIds } = scrubSyncedModelsWithCodexDenylist(input);
|
|
|
|
assert.deepEqual(
|
|
kept.map((m) => m.id),
|
|
["gpt-5.6-sol", "future-codex-experimental"]
|
|
);
|
|
assert.deepEqual(removedIds.sort(), ["gpt-5.4", "gpt-5.4-mini"]);
|
|
});
|
|
|
|
test("scrubSyncedModelsWithCodexDenylist is a no-op when nothing is denylisted", () => {
|
|
const input = [
|
|
{ id: "gpt-5.6-sol", name: "Sol", source: "imported" },
|
|
{ id: "gpt-5.5-low", name: "5.5 Low", source: "imported" },
|
|
] satisfies SyncedAvailableModel[];
|
|
const { kept, removedIds } = scrubSyncedModelsWithCodexDenylist(input);
|
|
assert.equal(removedIds.length, 0);
|
|
assert.equal(kept.length, 2);
|
|
});
|
|
|
|
test("resolveCodexCatalogAppVersion uses stable, source-qualified identities", () => {
|
|
const runtimeRoot = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-version-identity-"));
|
|
assert.equal(
|
|
resolveCodexCatalogAppVersion(
|
|
{
|
|
OMNIROUTE_BUILD_SHA: "abc123",
|
|
npm_package_version: "9.9.9",
|
|
},
|
|
{ runtimeRoot }
|
|
),
|
|
"build:abc123"
|
|
);
|
|
assert.equal(
|
|
resolveCodexCatalogAppVersion(
|
|
{
|
|
npm_package_version: "3.8.47",
|
|
},
|
|
{ runtimeRoot }
|
|
),
|
|
"pkg:3.8.47"
|
|
);
|
|
|
|
try {
|
|
fs.writeFileSync(path.join(runtimeRoot, "BUILD_SHA"), "sentinel-sha\n");
|
|
assert.equal(
|
|
resolveCodexCatalogAppVersion({}, { runtimeRoot, packageVersion: "3.8.47" }),
|
|
"build:sentinel-sha"
|
|
);
|
|
fs.rmSync(path.join(runtimeRoot, "BUILD_SHA"));
|
|
fs.writeFileSync(path.join(runtimeRoot, "package.json"), '{"version":"9.8.7"}\n');
|
|
assert.equal(resolveCodexCatalogAppVersion({}, { runtimeRoot }), "pkg:9.8.7");
|
|
assert.equal(
|
|
resolveCodexCatalogAppVersion({}, { runtimeRoot, packageVersion: "3.8.47" }),
|
|
"pkg:3.8.47"
|
|
);
|
|
assert.equal(resolveCodexCatalogAppVersion({}, { runtimeRoot, packageVersion: null }), null);
|
|
} finally {
|
|
fs.rmSync(runtimeRoot, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("resolveBootRevalidationReason only fires on first-start or upgrade", () => {
|
|
assert.equal(resolveBootRevalidationReason(null, "v2"), "first-start");
|
|
assert.equal(resolveBootRevalidationReason("", "v2"), "first-start");
|
|
assert.equal(resolveBootRevalidationReason("v1", "v2"), "upgrade");
|
|
assert.equal(resolveBootRevalidationReason("v2", "v2"), null);
|
|
});
|
|
|
|
test("executeCodexCatalogRevalidation records completion only after a full live sync", async () => {
|
|
const events: string[] = [];
|
|
const failed = await executeCodexCatalogRevalidation({
|
|
appVersion: "build:audit",
|
|
scrub: async () => {
|
|
events.push("scrub");
|
|
},
|
|
waitForReady: async () => {
|
|
events.push("ready");
|
|
},
|
|
liveResync: async () => {
|
|
events.push("sync");
|
|
return { attempted: 2, succeeded: 1 };
|
|
},
|
|
writeMarker: async () => {
|
|
events.push("marker");
|
|
return true;
|
|
},
|
|
logSuccess: () => {
|
|
events.push("log");
|
|
},
|
|
});
|
|
|
|
assert.equal(failed.complete, false);
|
|
assert.deepEqual(events, ["scrub", "ready", "sync"]);
|
|
|
|
events.length = 0;
|
|
const complete = await executeCodexCatalogRevalidation({
|
|
appVersion: "build:audit",
|
|
scrub: async () => {
|
|
events.push("scrub");
|
|
},
|
|
waitForReady: async () => {
|
|
events.push("ready");
|
|
},
|
|
liveResync: async () => {
|
|
events.push("sync");
|
|
return { attempted: 2, succeeded: 2 };
|
|
},
|
|
writeMarker: async () => {
|
|
events.push("marker");
|
|
return true;
|
|
},
|
|
logSuccess: () => {
|
|
events.push("log");
|
|
},
|
|
});
|
|
|
|
assert.equal(complete.complete, true);
|
|
assert.deepEqual(events, ["scrub", "ready", "sync", "marker", "log"]);
|
|
});
|
|
|
|
test("executeCodexCatalogRevalidation leaves an unknown-version run incomplete and unlogged", async () => {
|
|
const events: string[] = [];
|
|
const result = await executeCodexCatalogRevalidation({
|
|
appVersion: null,
|
|
scrub: async () => undefined,
|
|
waitForReady: async () => undefined,
|
|
liveResync: async () => ({ attempted: 0, succeeded: 0 }),
|
|
writeMarker: async () => {
|
|
events.push("marker");
|
|
return true;
|
|
},
|
|
logSuccess: () => {
|
|
events.push("log");
|
|
},
|
|
});
|
|
|
|
assert.equal(result.complete, false);
|
|
assert.deepEqual(events, []);
|
|
});
|
|
|
|
test("executeCodexCatalogRevalidation does not complete after readiness or marker failure", async () => {
|
|
let liveCalls = 0;
|
|
let markerCalls = 0;
|
|
let successLogs = 0;
|
|
const notReady = await executeCodexCatalogRevalidation({
|
|
appVersion: "build:audit",
|
|
scrub: async () => undefined,
|
|
waitForReady: async () => {
|
|
throw new Error("not ready");
|
|
},
|
|
liveResync: async () => {
|
|
liveCalls += 1;
|
|
return { attempted: 1, succeeded: 1 };
|
|
},
|
|
writeMarker: async () => {
|
|
markerCalls += 1;
|
|
return true;
|
|
},
|
|
logSuccess: () => {
|
|
successLogs += 1;
|
|
},
|
|
});
|
|
assert.equal(notReady.complete, false);
|
|
assert.equal(liveCalls, 0);
|
|
assert.equal(markerCalls, 0);
|
|
assert.equal(successLogs, 0);
|
|
|
|
const markerFailed = await executeCodexCatalogRevalidation({
|
|
appVersion: "build:audit",
|
|
scrub: async () => undefined,
|
|
waitForReady: async () => undefined,
|
|
liveResync: async () => ({ attempted: 1, succeeded: 1 }),
|
|
writeMarker: async () => false,
|
|
logSuccess: () => {
|
|
successLogs += 1;
|
|
},
|
|
});
|
|
assert.equal(markerFailed.complete, false);
|
|
assert.equal(successLogs, 0);
|
|
});
|
|
|
|
test("Codex revalidation coordinator coalesces startup and queues one init rerun", async () => {
|
|
let releaseFirst: (() => void) | undefined;
|
|
const firstGate = new Promise<void>((resolve) => {
|
|
releaseFirst = resolve;
|
|
});
|
|
const reasons: string[] = [];
|
|
let active = 0;
|
|
let maxActive = 0;
|
|
const request = createCodexCatalogRevalidationCoordinator(async (options) => {
|
|
reasons.push(options.reason);
|
|
active += 1;
|
|
maxActive = Math.max(maxActive, active);
|
|
if (reasons.length === 1) await firstGate;
|
|
active -= 1;
|
|
});
|
|
|
|
const startupA = request({ reason: "upgrade" });
|
|
const startupB = request({ reason: "upgrade" });
|
|
const initA = request({ reason: "init" });
|
|
const initB = request({ reason: "init" });
|
|
releaseFirst?.();
|
|
await Promise.all([startupA, startupB, initA, initB]);
|
|
|
|
assert.deepEqual(reasons, ["upgrade", "init"]);
|
|
assert.equal(maxActive, 1);
|
|
});
|
|
|
|
test("Codex revalidation coordinator does not lose init during active-run settlement", async () => {
|
|
let releaseFirst: (() => void) | undefined;
|
|
const firstGate = new Promise<void>((resolve) => {
|
|
releaseFirst = resolve;
|
|
});
|
|
const reasons: string[] = [];
|
|
const request = createCodexCatalogRevalidationCoordinator(async ({ reason }) => {
|
|
reasons.push(reason);
|
|
if (reasons.length === 1) await firstGate;
|
|
});
|
|
|
|
const activeRun = request({ reason: "upgrade" });
|
|
const settlementInit = firstGate.then(() =>
|
|
Promise.resolve().then(() => request({ reason: "init" }))
|
|
);
|
|
releaseFirst?.();
|
|
await Promise.all([activeRun, settlementInit]);
|
|
|
|
assert.deepEqual(reasons, ["upgrade", "init"]);
|
|
});
|