* fix(agents): preserve Claude thinking selections Model switches now resolve thinking against the target model's saved preference instead of resetting to the catalog default. Claude owns its configured-model defaults and legacy Fable alias handling, while persisted permission modes remain unchanged. * chore(claude): tag Fable compatibility shim * fix(app): resolve persisted model aliases * fix(models): preserve aliases for older clients * fix(models): hide compatibility aliases from listings * fix(settings): hide compatibility model aliases * test(claude): cover legacy catalog entry * test(app): expect mock thinking controls in schedules * fix(app): preserve queued thinking preferences * fix(app): reconcile schedule provider snapshots * fix(app): preserve schedule model selections * fix(claude): preserve exact configured model ids * fix(app): preserve committed thinking preferences * fix(app): preserve aliased schedule thinking
86 lines
3.0 KiB
TypeScript
86 lines
3.0 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import type { ProviderSnapshotEntry } from "./agent-types.js";
|
|
import { CompactProviderSnapshotSchema } from "./messages.js";
|
|
import { compactProviderSnapshot, expandProviderSnapshot } from "./provider-snapshot-codec.js";
|
|
|
|
const sharedThinking = [
|
|
{ id: "off", label: "Off", description: "No extra reasoning" },
|
|
{ id: "minimal", label: "Minimal", description: "Light reasoning" },
|
|
{ id: "low", label: "Low" },
|
|
{ id: "medium", label: "Medium", description: "Balanced reasoning" },
|
|
{ id: "high", label: "High", isDefault: true },
|
|
{ id: "xhigh", label: "XHigh", description: "Very deep reasoning" },
|
|
{ id: "max", label: "Max", description: "Extreme reasoning" },
|
|
];
|
|
|
|
function providerEntry(): ProviderSnapshotEntry {
|
|
return {
|
|
provider: "pi",
|
|
status: "ready",
|
|
enabled: true,
|
|
models: [
|
|
{
|
|
provider: "pi",
|
|
id: "openai/gpt-a",
|
|
aliases: ["openai/gpt-a-legacy"],
|
|
isSelectable: false,
|
|
label: "GPT A",
|
|
description: "openai/gpt-a",
|
|
metadata: { provider: "openai", modelId: "gpt-a" },
|
|
thinkingOptions: sharedThinking,
|
|
defaultThinkingOptionId: "high",
|
|
},
|
|
{
|
|
provider: "pi",
|
|
id: "openai/gpt-b",
|
|
label: "GPT B",
|
|
metadata: { provider: "openai", modelId: "gpt-b" },
|
|
thinkingOptions: structuredClone(sharedThinking),
|
|
defaultThinkingOptionId: "high",
|
|
},
|
|
],
|
|
modes: [{ id: "build", label: "Build" }],
|
|
};
|
|
}
|
|
|
|
describe("provider snapshot codec", () => {
|
|
it("shares repeated thinking options while preserving the public snapshot shape", () => {
|
|
const original = [providerEntry()];
|
|
const compact = compactProviderSnapshot(original);
|
|
const expanded = expandProviderSnapshot(compact);
|
|
|
|
expect(compact.thinkingSets).toHaveLength(1);
|
|
expect(compact.entries[0]?.models?.map((model) => model.thinkingSet)).toEqual([0, 0]);
|
|
expect(CompactProviderSnapshotSchema.parse(compact)).toEqual(compact);
|
|
expect(compact.entries[0]?.models?.[0]?.aliases).toEqual(["openai/gpt-a-legacy"]);
|
|
expect(compact.entries[0]?.models?.[0]?.isSelectable).toBe(false);
|
|
expect(expanded).toEqual(original);
|
|
expect(expanded[0]?.models?.[0]?.thinkingOptions).toBe(
|
|
expanded[0]?.models?.[1]?.thinkingOptions,
|
|
);
|
|
});
|
|
|
|
it("shrinks catalogs dominated by one repeated thinking set", () => {
|
|
const model = providerEntry().models?.[0];
|
|
if (!model) throw new Error("fixture model missing");
|
|
const entries: ProviderSnapshotEntry[] = [
|
|
{
|
|
provider: "pi",
|
|
status: "ready",
|
|
enabled: true,
|
|
models: Array.from({ length: 200 }, (_, index) => ({
|
|
...model,
|
|
id: `openai/model-${index}`,
|
|
label: `Model ${index}`,
|
|
metadata: { provider: "openai", modelId: `model-${index}` },
|
|
})),
|
|
},
|
|
];
|
|
|
|
const legacyBytes = JSON.stringify(entries).length;
|
|
const compactBytes = JSON.stringify(compactProviderSnapshot(entries)).length;
|
|
|
|
expect(compactBytes).toBeLessThan(legacyBytes * 0.5);
|
|
});
|
|
});
|