Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- **fix(db):** a partial `cavemanOutputMode` settings write keeps the stored values of the fields it leaves out, so sending only `autoClarity` or only `enabled` leaves the other two as they were ([#15206](https://github.com/diegosouzapw/OmniRoute/pull/15206)) — thanks @woodsonl
37 changes: 27 additions & 10 deletions src/lib/db/compression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,22 +114,19 @@ function normalizeCavemanConfig(value: unknown): CavemanConfig {
};
}

function normalizeCavemanOutputModeConfig(value: unknown): CavemanOutputModeConfig {
function normalizeCavemanOutputModeConfig(
value: unknown,
fallback: CavemanOutputModeConfig = DEFAULT_CAVEMAN_OUTPUT_MODE_CONFIG
): CavemanOutputModeConfig {
const record = toRecord(value);
return {
...DEFAULT_CAVEMAN_OUTPUT_MODE_CONFIG,
enabled:
typeof record.enabled === "boolean"
? record.enabled
: DEFAULT_CAVEMAN_OUTPUT_MODE_CONFIG.enabled,
enabled: typeof record.enabled === "boolean" ? record.enabled : fallback.enabled,
intensity:
record.intensity === "lite" || record.intensity === "full" || record.intensity === "ultra"
? record.intensity
: DEFAULT_CAVEMAN_OUTPUT_MODE_CONFIG.intensity,
: fallback.intensity,
autoClarity:
typeof record.autoClarity === "boolean"
? record.autoClarity
: DEFAULT_CAVEMAN_OUTPUT_MODE_CONFIG.autoClarity,
typeof record.autoClarity === "boolean" ? record.autoClarity : fallback.autoClarity,
};
}

Expand Down Expand Up @@ -566,6 +563,22 @@ function mergeLiteSettingsForWrite(
};
}

// Partial cavemanOutputMode writes replace the whole JSON row, and the read path fills
// missing fields with defaults. Take each missing or invalid field from the stored row instead.
function mergeCavemanOutputModeForWrite(
db: ReturnType<typeof getDbInstance>,
value: unknown
): CavemanOutputModeConfig {
const existingRow = db
.prepare("SELECT value FROM key_value WHERE namespace = ? AND key = ?")
.get(NAMESPACE, "cavemanOutputMode") as { value: unknown } | undefined;
// getCompressionSettings skips non-text (BLOB) rows, so treat them as absent here too.
const existing = normalizeCavemanOutputModeConfig(
typeof existingRow?.value === "string" ? parseJsonSafe(existingRow.value) : undefined
);
return normalizeCavemanOutputModeConfig(value, existing);
}

// Read the stored `engines` JSON row, keeping only well-formed `{enabled, level?}` entries for
// known engine ids. Returns null when no usable row exists so the caller falls back to deriving
// the map from the legacy fields (B-backfill, migration 102).
Expand Down Expand Up @@ -928,6 +941,10 @@ export async function updateCompressionSettings(
insert.run(NAMESPACE, key, JSON.stringify(mergeLiteSettingsForWrite(db, value)));
continue;
}
if (key === "cavemanOutputMode") {
insert.run(NAMESPACE, key, JSON.stringify(mergeCavemanOutputModeForWrite(db, value)));
continue;
}
insert.run(NAMESPACE, key, JSON.stringify(value));
}
});
Expand Down
26 changes: 24 additions & 2 deletions tests/unit/api/compression/compression-api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ describe("Compression Settings API Schema Validation", () => {
});
});

// ─── Route round-trip: engines map + activeComboId ─────────────────────────
// ─── Route round-trip: engines map, activeComboId, cavemanOutputMode ───────
// Mirrors the mcp-accessibility-config test harness: allocate a temp DATA_DIR,
// import route + DB modules, tear down in after().

Expand All @@ -136,7 +136,7 @@ function makeRequest(method: string, body?: unknown): Request {
}) as any;
}

describe("settings/compression route — engines + activeComboId", () => {
describe("settings/compression route — engines, activeComboId, cavemanOutputMode", () => {
beforeEach(() => {
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
Expand Down Expand Up @@ -225,4 +225,26 @@ describe("settings/compression route — engines + activeComboId", () => {
const putRes2 = await route.PUT(makeRequest("PUT", { enabled: false, enginesExplicit: false }));
assert.equal(putRes2.status, 200);
});

it("PUT a partial cavemanOutputMode keeps the stored fields it omits", async () => {
await route.PUT(
makeRequest("PUT", {
cavemanOutputMode: { enabled: true, intensity: "ultra", autoClarity: true },
})
);
core.resetDbInstance();

const putRes = await route.PUT(
makeRequest("PUT", { cavemanOutputMode: { autoClarity: false } })
);
assert.equal(putRes.status, 200);
core.resetDbInstance();

const body = await (await route.GET(makeRequest("GET"))).json();
assert.deepEqual(body.cavemanOutputMode, {
enabled: true,
intensity: "ultra",
autoClarity: false,
});
});
});
114 changes: 114 additions & 0 deletions tests/unit/compression/db.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import type { CavemanOutputModeConfig } from "../../../open-sse/services/compression/types.ts";

const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-compression-db-"));
const ORIGINAL_DATA_DIR = process.env.DATA_DIR;
Expand Down Expand Up @@ -121,6 +122,119 @@ describe("updateCompressionSettings", () => {
assert.equal(settings.lite?.maxToolLength, undefined);
});

describe("a partial cavemanOutputMode write", () => {
const writeThenRead = async (cavemanOutputMode: Partial<CavemanOutputModeConfig>) => {
core.resetDbInstance();
await updateCompressionSettings({ cavemanOutputMode } as Parameters<
typeof updateCompressionSettings
>[0]);
core.resetDbInstance();
return (await getCompressionSettings()).cavemanOutputMode;
};
const seedStoredRow = (value: unknown) =>
core
.getDbInstance()
.prepare("INSERT OR REPLACE INTO key_value (namespace, key, value) VALUES (?, ?, ?)")
.run("compression", "cavemanOutputMode", value);

it("keeps the stored enabled and intensity when it only sends autoClarity", async () => {
await updateCompressionSettings({
cavemanOutputMode: { enabled: true, intensity: "ultra", autoClarity: true },
});
assert.deepEqual(await writeThenRead({ autoClarity: false }), {
enabled: true,
intensity: "ultra",
autoClarity: false,
});
});

it("keeps a stored autoClarity false when it only sends enabled", async () => {
await updateCompressionSettings({
cavemanOutputMode: { enabled: false, intensity: "full", autoClarity: false },
});
assert.deepEqual(await writeThenRead({ enabled: true }), {
enabled: true,
intensity: "full",
autoClarity: false,
});
});

it("applies each sent field over the stored row, and an empty write changes nothing", async () => {
await updateCompressionSettings({
cavemanOutputMode: { enabled: true, intensity: "ultra", autoClarity: false },
});
assert.deepEqual(await writeThenRead({ enabled: false }), {
enabled: false,
intensity: "ultra",
autoClarity: false,
});
assert.deepEqual(await writeThenRead({ autoClarity: true }), {
enabled: false,
intensity: "ultra",
autoClarity: true,
});
assert.deepEqual(await writeThenRead({ enabled: true, intensity: "lite" }), {
enabled: true,
intensity: "lite",
autoClarity: true,
});
assert.deepEqual(await writeThenRead({}), {
enabled: true,
intensity: "lite",
autoClarity: true,
});
});

it("takes each present-but-invalid field from the stored row", async () => {
await updateCompressionSettings({
cavemanOutputMode: { enabled: true, intensity: "ultra", autoClarity: false },
});
const invalid = { enabled: "yes", intensity: "mega", autoClarity: 1 };
assert.deepEqual(
await writeThenRead(invalid as unknown as Partial<CavemanOutputModeConfig>),
{
enabled: true,
intensity: "ultra",
autoClarity: false,
}
);
});

it("keeps the valid fields of a partially populated stored row", async () => {
seedStoredRow(JSON.stringify({ intensity: "ultra", autoClarity: false }));
assert.deepEqual(await writeThenRead({ enabled: true }), {
enabled: true,
intensity: "ultra",
autoClarity: false,
});
});

it("treats a non-text, unparseable, or null stored row as absent", async () => {
seedStoredRow(
Buffer.from(JSON.stringify({ enabled: true, intensity: "ultra", autoClarity: true }))
);
assert.deepEqual(await writeThenRead({ autoClarity: false }), {
enabled: false,
intensity: "lite",
autoClarity: false,
});

seedStoredRow("{not valid json");
assert.deepEqual(await writeThenRead({ enabled: true }), {
enabled: true,
intensity: "lite",
autoClarity: true,
});

seedStoredRow("null");
assert.deepEqual(await writeThenRead({ autoClarity: false }), {
enabled: false,
intensity: "lite",
autoClarity: false,
});
});
});

it("updates defaultMode", async () => {
await updateCompressionSettings({ defaultMode: "lite" } as any);
const settings = await getCompressionSettings();
Expand Down
Loading