mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-17 20:32:25 +03:00
fix(opencode-plugin): report disk-cache age in the stale-fallback warning (#13426)
Merged after boarding with #13185 into one worktree cut from `release/v3.8.51` (both verified as ancestors of the combined HEAD before validating). **Evidence** - Full `@omniroute/opencode-plugin` suite — not just this PR's file: **373/373 pass** across all 26 test files. - Gates on the combined tree: `check-changelog-integrity` PASS, `check-complexity` PASS (2842 vs baseline 3218), `check-cognitive-complexity` PASS (1284 vs 1437), `typecheck:core` PASS. - `check-file-size`: no violation attributable to this PR. **One note for the record.** On a first run the suite reported `scaffold: built ESM default export resolves with the v1 plugin shape` failing with `ERR_MODULE_NOT_FOUND … dist/index.js`. That was a fresh worktree without the plugin built, not a defect in this PR — after `npm run build` in the workspace, all 373 pass. Flagging it because that failure reads exactly like a PR bug and could easily be misattributed to you on a future run. Thanks, @RaviTharuma — putting the snapshot age in the warning turns "using stale disk cache" from a fact into something an operator can act on, and you covered `snapshotAgeLabel` with both the numeric-`writtenAt` and missing-`writtenAt` cases rather than only the happy path.
This commit is contained in:
@@ -5556,9 +5556,17 @@ export function createOmniRouteConfigHook(
|
||||
if (modelsFetchThrew && wantDiskCache && !warmSnapshot) {
|
||||
const snapshot = await diskSnapshotReader(resolved.providerId, snapshotFingerprint);
|
||||
if (snapshot && snapshot.rawModels.length > 0) {
|
||||
// Report snapshot age like the warm-startup path already does:
|
||||
// "stale" alone reads as a transient blip, so a week-old catalog
|
||||
// is indistinguishable from a five-minute-old one.
|
||||
const snapshotAge = snapshot.writtenAt;
|
||||
const snapshotAgeLabel =
|
||||
typeof snapshotAge === "number"
|
||||
? `${Math.round((Date.now() - snapshotAge) / 3_600_000)}h`
|
||||
: "unknown";
|
||||
logAt(
|
||||
"warn",
|
||||
`config shim: /v1/models unreachable; using stale disk cache (${snapshot.rawModels.length} models)`
|
||||
`config shim: /v1/models unreachable; using stale disk cache (${snapshot.rawModels.length} models, age ${snapshotAgeLabel})`
|
||||
);
|
||||
localRawModels = snapshot.rawModels;
|
||||
localRawCombos = snapshot.rawCombos;
|
||||
|
||||
@@ -481,10 +481,7 @@ test("config: combos fetcher throws → emit models-only catalog (no combos in m
|
||||
];
|
||||
assert.ok(entry);
|
||||
const ids = Object.keys(entry.models).sort();
|
||||
assert.deepEqual(ids, [
|
||||
"claude-sonnet-4-6",
|
||||
"gemini-3-flash",
|
||||
]);
|
||||
assert.deepEqual(ids, ["claude-sonnet-4-6", "gemini-3-flash"]);
|
||||
assert.equal(entry.models["claude-tier"], undefined, "no combo entry");
|
||||
assert.ok(
|
||||
logger.entries.some((e) => String(e[0]).includes("/api/combos fetch failed")),
|
||||
@@ -1041,11 +1038,7 @@ test("config: features.enrichment=false skips enrichment fetch + keeps raw-id na
|
||||
];
|
||||
assert.ok(entry);
|
||||
assert.equal(enrichmentFetcher.callCount(), 0, "enrichment fetch suppressed by feature flag");
|
||||
assert.equal(
|
||||
entry.models["claude-sonnet-4-6"].name,
|
||||
"claude-sonnet-4-6",
|
||||
"raw id retained"
|
||||
);
|
||||
assert.equal(entry.models["claude-sonnet-4-6"].name, "claude-sonnet-4-6", "raw id retained");
|
||||
});
|
||||
|
||||
test("config: enrichment fetcher throws → soft-fail (warn + raw-id static catalog)", async () => {
|
||||
@@ -1068,11 +1061,7 @@ test("config: enrichment fetcher throws → soft-fail (warn + raw-id static cata
|
||||
"opencode-omniroute"
|
||||
];
|
||||
assert.ok(entry, "static block still published on enrichment failure");
|
||||
assert.equal(
|
||||
entry.models["claude-sonnet-4-6"].name,
|
||||
"claude-sonnet-4-6",
|
||||
"raw id retained"
|
||||
);
|
||||
assert.equal(entry.models["claude-sonnet-4-6"].name, "claude-sonnet-4-6", "raw id retained");
|
||||
assert.equal(enrichmentFetcher.callCount(), 1);
|
||||
assert.ok(
|
||||
logger.entries.some((e) => String(e[0]).includes("/api/pricing/models fetch failed")),
|
||||
@@ -1270,10 +1259,7 @@ test("config: diskCache hydrates stale snapshot when /v1/models throws", async (
|
||||
const entry = (input as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider[
|
||||
"opencode-omniroute"
|
||||
];
|
||||
assert.ok(
|
||||
entry.models["claude-sonnet-4-6"],
|
||||
"stale snapshot hydrated into static block"
|
||||
);
|
||||
assert.ok(entry.models["claude-sonnet-4-6"], "stale snapshot hydrated into static block");
|
||||
assert.equal(
|
||||
entry.models["claude-sonnet-4-6"].name,
|
||||
"Claude Sonnet 4.6 (cached)",
|
||||
@@ -1281,14 +1267,95 @@ test("config: diskCache hydrates stale snapshot when /v1/models throws", async (
|
||||
);
|
||||
assert.equal(writes, 0, "disk write skipped when live fetch failed");
|
||||
assert.ok(
|
||||
logger.entries.some((e) =>
|
||||
String(e[0]).includes("using stale disk cache") ||
|
||||
String(e[0]).includes("warm startup from disk snapshot")
|
||||
logger.entries.some(
|
||||
(e) =>
|
||||
String(e[0]).includes("using stale disk cache") ||
|
||||
String(e[0]).includes("warm startup from disk snapshot")
|
||||
),
|
||||
"disk-cache hydration breadcrumb emitted"
|
||||
);
|
||||
});
|
||||
|
||||
// The stale-fallback branch (`modelsFetchThrew && wantDiskCache && !warmSnapshot`)
|
||||
// only runs when the warm-startup read found nothing — a snapshot can appear on
|
||||
// disk between that first read and the live fetch failing (e.g. another OC
|
||||
// process instance wrote one concurrently). A stateful reader simulates that:
|
||||
// empty on the warm-startup read, populated by the time the fallback re-reads.
|
||||
function emptyThenSnapshotReader(
|
||||
snapshot: Omit<
|
||||
Awaited<ReturnType<typeof import("../src/index.js").defaultDiskSnapshotReader>> & object,
|
||||
never
|
||||
>
|
||||
): typeof import("../src/index.js").defaultDiskSnapshotReader {
|
||||
let calls = 0;
|
||||
return (async () => {
|
||||
calls++;
|
||||
return calls === 1 ? undefined : snapshot;
|
||||
}) as typeof import("../src/index.js").defaultDiskSnapshotReader;
|
||||
}
|
||||
|
||||
test("config: stale-fallback warning reports the disk snapshot age in hours", async () => {
|
||||
const readAuthJson = stubReadAuthJson({
|
||||
"opencode-omniroute": { type: "api", key: "sk-test", baseURL: "https://or.example/v1" },
|
||||
});
|
||||
const fetcher = throwingModelsFetcher();
|
||||
const combosFetcher = stubCombosFetcher([]);
|
||||
const logger = captureWarn();
|
||||
|
||||
const writtenAt = Date.now() - 2 * 3_600_000; // 2h old
|
||||
const diskSnapshotReader = emptyThenSnapshotReader({
|
||||
rawModels: [MODEL_CLAUDE],
|
||||
rawCombos: [],
|
||||
rawEnrichment: new Map(),
|
||||
rawCompressionCombos: [],
|
||||
rawConnections: [],
|
||||
writtenAt,
|
||||
});
|
||||
|
||||
const hook = createOmniRouteConfigHook(
|
||||
{ providerId: "omniroute", features: { diskCache: true } },
|
||||
{ readAuthJson, fetcher, combosFetcher, diskSnapshotReader, logger }
|
||||
);
|
||||
|
||||
await hook(makeInput());
|
||||
|
||||
assert.ok(
|
||||
logger.entries.some((e) => String(e[0]).includes("using stale disk cache (1 models, age 2h)")),
|
||||
"stale-fallback warning includes the computed snapshot age"
|
||||
);
|
||||
});
|
||||
|
||||
test('config: stale-fallback warning falls back to "unknown" age without writtenAt', async () => {
|
||||
const readAuthJson = stubReadAuthJson({
|
||||
"opencode-omniroute": { type: "api", key: "sk-test", baseURL: "https://or.example/v1" },
|
||||
});
|
||||
const fetcher = throwingModelsFetcher();
|
||||
const combosFetcher = stubCombosFetcher([]);
|
||||
const logger = captureWarn();
|
||||
|
||||
const diskSnapshotReader = emptyThenSnapshotReader({
|
||||
rawModels: [MODEL_CLAUDE],
|
||||
rawCombos: [],
|
||||
rawEnrichment: new Map(),
|
||||
rawCompressionCombos: [],
|
||||
rawConnections: [],
|
||||
});
|
||||
|
||||
const hook = createOmniRouteConfigHook(
|
||||
{ providerId: "omniroute", features: { diskCache: true } },
|
||||
{ readAuthJson, fetcher, combosFetcher, diskSnapshotReader, logger }
|
||||
);
|
||||
|
||||
await hook(makeInput());
|
||||
|
||||
assert.ok(
|
||||
logger.entries.some((e) =>
|
||||
String(e[0]).includes("using stale disk cache (1 models, age unknown)")
|
||||
),
|
||||
'stale-fallback warning falls back to "unknown" when writtenAt is absent'
|
||||
);
|
||||
});
|
||||
|
||||
test("config: cached rawEnrichment from earlier provider hook is reused (no refetch)", async () => {
|
||||
const readAuthJson = stubReadAuthJson({
|
||||
"opencode-omniroute": { type: "api", key: "sk-shared", baseURL: "https://or.example/v1" },
|
||||
@@ -1376,10 +1443,7 @@ test("config: providerTag (default-on) prepends '<provider> - ' to enriched raw-
|
||||
"opencode-omniroute"
|
||||
];
|
||||
assert.ok(entry);
|
||||
assert.equal(
|
||||
entry.models["claude-sonnet-4-6"].name,
|
||||
"Claude - Claude Sonnet 4.6"
|
||||
);
|
||||
assert.equal(entry.models["claude-sonnet-4-6"].name, "Claude - Claude Sonnet 4.6");
|
||||
assert.equal(entry.models["gemini-3-flash"].name, "Gemini - Gemini 3 Flash");
|
||||
// Combos stay untouched — `Combo: ` prefix already conveys multi-upstream.
|
||||
assert.equal(entry.models["claude-tier"].name, "Claude Tier");
|
||||
@@ -1495,10 +1559,7 @@ test("config: providerTag is idempotent — second hook call doesn't double-suff
|
||||
const entryA = (inputA as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider[
|
||||
"opencode-omniroute"
|
||||
];
|
||||
assert.equal(
|
||||
entryA.models["claude-sonnet-4-6"].name,
|
||||
"Claude - Claude Sonnet 4.6"
|
||||
);
|
||||
assert.equal(entryA.models["claude-sonnet-4-6"].name, "Claude - Claude Sonnet 4.6");
|
||||
|
||||
// Second invocation (cache hit) — name must still be single-suffixed.
|
||||
const inputB = makeInput();
|
||||
@@ -1506,10 +1567,7 @@ test("config: providerTag is idempotent — second hook call doesn't double-suff
|
||||
const entryB = (inputB as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider[
|
||||
"opencode-omniroute"
|
||||
];
|
||||
assert.equal(
|
||||
entryB.models["claude-sonnet-4-6"].name,
|
||||
"Claude - Claude Sonnet 4.6"
|
||||
);
|
||||
assert.equal(entryB.models["claude-sonnet-4-6"].name, "Claude - Claude Sonnet 4.6");
|
||||
});
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
- **fix(opencode-plugin):** The stale disk-cache fallback warning now reports the snapshot's age (`using stale disk cache (N models, age 168h)`), matching the existing warm-startup log. Previously a week-old catalog was indistinguishable from a five-minute-old one, so silent model drift went unnoticed. (#13426)
|
||||
Reference in New Issue
Block a user