mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-04 07:24:04 +00:00
refactor: share sessions patch test helpers
This commit is contained in:
@@ -9,6 +9,11 @@ import { applySessionsPatchToStore } from "./sessions-patch.js";
|
||||
const SUBAGENT_MODEL = "synthetic/hf:moonshotai/Kimi-K2.5";
|
||||
const KIMI_SUBAGENT_KEY = "agent:kimi:subagent:child";
|
||||
const MAIN_SESSION_KEY = "agent:main:main";
|
||||
const ANTHROPIC_SONNET_MODEL = "anthropic/claude-sonnet-4-6";
|
||||
const ANTHROPIC_SONNET_ID = "claude-sonnet-4-6";
|
||||
const ANTHROPIC_OPUS_ID = "claude-opus-4-6";
|
||||
const OPENAI_GPT_MODEL = "openai/gpt-5.4";
|
||||
const OPENAI_GPT_ID = "gpt-5.4";
|
||||
const EMPTY_CFG = {} as OpenClawConfig;
|
||||
|
||||
type ApplySessionsPatchArgs = Parameters<typeof applySessionsPatchToStore>[0];
|
||||
@@ -52,6 +57,100 @@ function expectPatchError(
|
||||
expect(result.error.message).toContain(message);
|
||||
}
|
||||
|
||||
function mainStoreEntry(overrides: Partial<SessionEntry>): Record<string, SessionEntry> {
|
||||
return {
|
||||
[MAIN_SESSION_KEY]: {
|
||||
sessionId: "sess",
|
||||
updatedAt: 1,
|
||||
...overrides,
|
||||
} as SessionEntry,
|
||||
};
|
||||
}
|
||||
|
||||
function mainAuthOverrideStore(overrides: Partial<SessionEntry>): Record<string, SessionEntry> {
|
||||
return mainStoreEntry({
|
||||
providerOverride: "anthropic",
|
||||
modelOverride: ANTHROPIC_OPUS_ID,
|
||||
authProfileOverrideSource: "user",
|
||||
...overrides,
|
||||
});
|
||||
}
|
||||
|
||||
function catalogEntry(ref: string, name?: string) {
|
||||
const separator = ref.indexOf("/");
|
||||
if (separator < 0) {
|
||||
throw new Error(`model ref must include provider: ${ref}`);
|
||||
}
|
||||
const id = ref.slice(separator + 1);
|
||||
return {
|
||||
provider: ref.slice(0, separator),
|
||||
id,
|
||||
name: name ?? id,
|
||||
};
|
||||
}
|
||||
|
||||
function loadCatalog(...refs: string[]): ApplySessionsPatchArgs["loadGatewayModelCatalog"] {
|
||||
return async () => refs.map((ref) => catalogEntry(ref));
|
||||
}
|
||||
|
||||
function expectModelSelection(
|
||||
entry: SessionEntry,
|
||||
providerOverride: string | undefined,
|
||||
modelOverride: string | undefined,
|
||||
) {
|
||||
expect(entry.providerOverride).toBe(providerOverride);
|
||||
expect(entry.modelOverride).toBe(modelOverride);
|
||||
}
|
||||
|
||||
async function applyMainModelPatch(params: {
|
||||
store?: Record<string, SessionEntry>;
|
||||
cfg?: OpenClawConfig;
|
||||
model: string | null;
|
||||
catalogRefs?: string[];
|
||||
}) {
|
||||
return expectPatchOk(
|
||||
await runPatch({
|
||||
store: params.store,
|
||||
cfg: params.cfg,
|
||||
patch: { key: MAIN_SESSION_KEY, model: params.model },
|
||||
loadGatewayModelCatalog:
|
||||
params.catalogRefs === undefined ? undefined : loadCatalog(...params.catalogRefs),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
async function expectProviderChangeClearsAuthOverride(store: Record<string, SessionEntry>) {
|
||||
const entry = await applyMainModelPatch({
|
||||
store,
|
||||
model: OPENAI_GPT_MODEL,
|
||||
catalogRefs: [OPENAI_GPT_MODEL],
|
||||
});
|
||||
expectModelSelection(entry, "openai", OPENAI_GPT_ID);
|
||||
expectAuthOverride(entry, { profile: undefined });
|
||||
}
|
||||
|
||||
function expectAuthOverride(
|
||||
entry: SessionEntry,
|
||||
expected: {
|
||||
profile: string | undefined;
|
||||
source?: string;
|
||||
compactionCount?: number;
|
||||
},
|
||||
) {
|
||||
expect(entry.authProfileOverride).toBe(expected.profile);
|
||||
if (expected.profile === undefined) {
|
||||
expect(entry.authProfileOverrideSource).toBeUndefined();
|
||||
expect(entry.authProfileOverrideCompactionCount).toBeUndefined();
|
||||
return;
|
||||
}
|
||||
expect(entry.authProfileOverrideSource).toBe(expected.source ?? "user");
|
||||
if (expected.compactionCount === undefined) {
|
||||
expect(entry.authProfileOverrideCompactionCount).toBeUndefined();
|
||||
} else {
|
||||
expect(entry.authProfileOverrideCompactionCount).toBe(expected.compactionCount);
|
||||
}
|
||||
}
|
||||
|
||||
async function applySubagentModelPatch(cfg: OpenClawConfig) {
|
||||
return expectPatchOk(
|
||||
await runPatch({
|
||||
@@ -62,7 +161,7 @@ async function applySubagentModelPatch(cfg: OpenClawConfig) {
|
||||
model: SUBAGENT_MODEL,
|
||||
},
|
||||
loadGatewayModelCatalog: async () => [
|
||||
{ provider: "anthropic", id: "claude-sonnet-4-6", name: "sonnet" },
|
||||
{ provider: "anthropic", id: ANTHROPIC_SONNET_ID, name: "sonnet" },
|
||||
{ provider: "synthetic", id: "hf:moonshotai/Kimi-K2.5", name: "kimi" },
|
||||
],
|
||||
}),
|
||||
@@ -100,9 +199,9 @@ function createAllowlistedAnthropicModelCfg(): OpenClawConfig {
|
||||
return {
|
||||
agents: {
|
||||
defaults: {
|
||||
model: { primary: "openai/gpt-5.4" },
|
||||
model: { primary: OPENAI_GPT_MODEL },
|
||||
models: {
|
||||
"anthropic/claude-sonnet-4-6": { alias: "sonnet" },
|
||||
[ANTHROPIC_SONNET_MODEL]: { alias: "sonnet" },
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -184,7 +283,7 @@ describe("gateway sessions patch", () => {
|
||||
sessionFile: "stale.jsonl",
|
||||
label: "Stale Session",
|
||||
sendPolicy: "deny",
|
||||
modelOverride: "gpt-5.4",
|
||||
modelOverride: OPENAI_GPT_ID,
|
||||
responseUsage: "tokens",
|
||||
parentSessionKey: "agent:main:main",
|
||||
} as SessionEntry,
|
||||
@@ -202,16 +301,14 @@ describe("gateway sessions patch", () => {
|
||||
expect(entry.sessionFile).toBeUndefined();
|
||||
expect(entry.label).toBeUndefined();
|
||||
expect(entry.sendPolicy).toBe("deny");
|
||||
expect(entry.modelOverride).toBe("gpt-5.4");
|
||||
expect(entry.modelOverride).toBe(OPENAI_GPT_ID);
|
||||
expect(entry.responseUsage).toBe("tokens");
|
||||
expect(entry.parentSessionKey).toBe("agent:main:main");
|
||||
expect(entry.fastMode).toBe(true);
|
||||
});
|
||||
|
||||
test("clears fastMode when patch sets null", async () => {
|
||||
const store: Record<string, SessionEntry> = {
|
||||
[MAIN_SESSION_KEY]: { fastMode: true } as SessionEntry,
|
||||
};
|
||||
const store = mainStoreEntry({ fastMode: true });
|
||||
const entry = expectPatchOk(
|
||||
await runPatch({
|
||||
store,
|
||||
@@ -276,223 +373,125 @@ describe("gateway sessions patch", () => {
|
||||
});
|
||||
|
||||
test("preserves same-provider auth overrides when model patch changes", async () => {
|
||||
const store: Record<string, SessionEntry> = {
|
||||
"agent:main:main": {
|
||||
sessionId: "sess",
|
||||
updatedAt: 1,
|
||||
providerOverride: "anthropic",
|
||||
modelOverride: "claude-opus-4-6",
|
||||
authProfileOverride: "anthropic:default",
|
||||
authProfileOverrideSource: "user",
|
||||
authProfileOverrideCompactionCount: 3,
|
||||
} as SessionEntry,
|
||||
};
|
||||
const entry = expectPatchOk(
|
||||
await runPatch({
|
||||
store,
|
||||
patch: { key: MAIN_SESSION_KEY, model: "anthropic/claude-sonnet-4-6" },
|
||||
loadGatewayModelCatalog: async () => [
|
||||
{ provider: "anthropic", id: "claude-sonnet-4-6", name: "claude-sonnet-4-6" },
|
||||
],
|
||||
}),
|
||||
);
|
||||
expect(entry.providerOverride).toBe("anthropic");
|
||||
expect(entry.modelOverride).toBe("claude-sonnet-4-6");
|
||||
expect(entry.authProfileOverride).toBe("anthropic:default");
|
||||
expect(entry.authProfileOverrideSource).toBe("user");
|
||||
expect(entry.authProfileOverrideCompactionCount).toBe(3);
|
||||
const store = mainAuthOverrideStore({
|
||||
authProfileOverride: "anthropic:default",
|
||||
authProfileOverrideCompactionCount: 3,
|
||||
});
|
||||
const entry = await applyMainModelPatch({
|
||||
store,
|
||||
model: ANTHROPIC_SONNET_MODEL,
|
||||
catalogRefs: [ANTHROPIC_SONNET_MODEL],
|
||||
});
|
||||
expectModelSelection(entry, "anthropic", ANTHROPIC_SONNET_ID);
|
||||
expectAuthOverride(entry, { profile: "anthropic:default", compactionCount: 3 });
|
||||
});
|
||||
|
||||
test("preserves auth overrides for provider-auth aliases when model patch changes", async () => {
|
||||
const store: Record<string, SessionEntry> = {
|
||||
"agent:main:main": {
|
||||
sessionId: "sess-alias",
|
||||
updatedAt: 1,
|
||||
providerOverride: "byteplus",
|
||||
modelOverride: "seedance-1-0-lite-t2v-250428",
|
||||
authProfileOverride: "byteplus:work",
|
||||
authProfileOverrideSource: "user",
|
||||
authProfileOverrideCompactionCount: 2,
|
||||
} as SessionEntry,
|
||||
};
|
||||
const entry = expectPatchOk(
|
||||
await runPatch({
|
||||
store,
|
||||
patch: { key: MAIN_SESSION_KEY, model: "byteplus-plan/ark-code-latest" },
|
||||
loadGatewayModelCatalog: async () => [
|
||||
{ provider: "byteplus-plan", id: "ark-code-latest", name: "ark-code-latest" },
|
||||
],
|
||||
}),
|
||||
);
|
||||
expect(entry.providerOverride).toBe("byteplus-plan");
|
||||
expect(entry.modelOverride).toBe("ark-code-latest");
|
||||
expect(entry.authProfileOverride).toBe("byteplus:work");
|
||||
expect(entry.authProfileOverrideSource).toBe("user");
|
||||
expect(entry.authProfileOverrideCompactionCount).toBe(2);
|
||||
const store = mainStoreEntry({
|
||||
sessionId: "sess-alias",
|
||||
providerOverride: "byteplus",
|
||||
modelOverride: "seedance-1-0-lite-t2v-250428",
|
||||
authProfileOverride: "byteplus:work",
|
||||
authProfileOverrideSource: "user",
|
||||
authProfileOverrideCompactionCount: 2,
|
||||
});
|
||||
const entry = await applyMainModelPatch({
|
||||
store,
|
||||
model: "byteplus-plan/ark-code-latest",
|
||||
catalogRefs: ["byteplus-plan/ark-code-latest"],
|
||||
});
|
||||
expectModelSelection(entry, "byteplus-plan", "ark-code-latest");
|
||||
expectAuthOverride(entry, { profile: "byteplus:work", compactionCount: 2 });
|
||||
});
|
||||
|
||||
test("preserves unprefixed auth overrides when existing provider matches model patch", async () => {
|
||||
const store: Record<string, SessionEntry> = {
|
||||
"agent:main:main": {
|
||||
sessionId: "sess-unprefixed-same-provider",
|
||||
updatedAt: 1,
|
||||
providerOverride: "anthropic",
|
||||
modelOverride: "claude-opus-4-6",
|
||||
authProfileOverride: "work",
|
||||
authProfileOverrideSource: "user",
|
||||
authProfileOverrideCompactionCount: 4,
|
||||
} as SessionEntry,
|
||||
};
|
||||
const entry = expectPatchOk(
|
||||
await runPatch({
|
||||
store,
|
||||
patch: { key: MAIN_SESSION_KEY, model: "anthropic/claude-sonnet-4-6" },
|
||||
loadGatewayModelCatalog: async () => [
|
||||
{ provider: "anthropic", id: "claude-sonnet-4-6", name: "sonnet" },
|
||||
],
|
||||
}),
|
||||
);
|
||||
expect(entry.providerOverride).toBe("anthropic");
|
||||
expect(entry.modelOverride).toBe("claude-sonnet-4-6");
|
||||
expect(entry.authProfileOverride).toBe("work");
|
||||
expect(entry.authProfileOverrideSource).toBe("user");
|
||||
expect(entry.authProfileOverrideCompactionCount).toBe(4);
|
||||
const store = mainAuthOverrideStore({
|
||||
sessionId: "sess-unprefixed-same-provider",
|
||||
authProfileOverride: "work",
|
||||
authProfileOverrideCompactionCount: 4,
|
||||
});
|
||||
const entry = await applyMainModelPatch({
|
||||
store,
|
||||
model: ANTHROPIC_SONNET_MODEL,
|
||||
catalogRefs: [ANTHROPIC_SONNET_MODEL],
|
||||
});
|
||||
expectModelSelection(entry, "anthropic", ANTHROPIC_SONNET_ID);
|
||||
expectAuthOverride(entry, { profile: "work", compactionCount: 4 });
|
||||
});
|
||||
|
||||
test("preserves unprefixed auth overrides when existing provider is the default", async () => {
|
||||
const store: Record<string, SessionEntry> = {
|
||||
"agent:main:main": {
|
||||
sessionId: "sess-unprefixed-default-provider",
|
||||
updatedAt: 1,
|
||||
authProfileOverride: "work",
|
||||
authProfileOverrideSource: "user",
|
||||
authProfileOverrideCompactionCount: 4,
|
||||
} as SessionEntry,
|
||||
};
|
||||
const entry = expectPatchOk(
|
||||
await runPatch({
|
||||
cfg: {
|
||||
agents: {
|
||||
defaults: {
|
||||
model: { primary: "anthropic/claude-opus-4-6" },
|
||||
},
|
||||
const store = mainStoreEntry({
|
||||
sessionId: "sess-unprefixed-default-provider",
|
||||
authProfileOverride: "work",
|
||||
authProfileOverrideSource: "user",
|
||||
authProfileOverrideCompactionCount: 4,
|
||||
});
|
||||
const entry = await applyMainModelPatch({
|
||||
cfg: {
|
||||
agents: {
|
||||
defaults: {
|
||||
model: { primary: `anthropic/${ANTHROPIC_OPUS_ID}` },
|
||||
},
|
||||
} as OpenClawConfig,
|
||||
store,
|
||||
patch: { key: MAIN_SESSION_KEY, model: "anthropic/claude-sonnet-4-6" },
|
||||
loadGatewayModelCatalog: async () => [
|
||||
{ provider: "anthropic", id: "claude-sonnet-4-6", name: "sonnet" },
|
||||
],
|
||||
}),
|
||||
);
|
||||
expect(entry.providerOverride).toBe("anthropic");
|
||||
expect(entry.modelOverride).toBe("claude-sonnet-4-6");
|
||||
expect(entry.authProfileOverride).toBe("work");
|
||||
expect(entry.authProfileOverrideSource).toBe("user");
|
||||
expect(entry.authProfileOverrideCompactionCount).toBe(4);
|
||||
},
|
||||
} as OpenClawConfig,
|
||||
store,
|
||||
model: ANTHROPIC_SONNET_MODEL,
|
||||
catalogRefs: [ANTHROPIC_SONNET_MODEL],
|
||||
});
|
||||
expectModelSelection(entry, "anthropic", ANTHROPIC_SONNET_ID);
|
||||
expectAuthOverride(entry, { profile: "work", compactionCount: 4 });
|
||||
});
|
||||
|
||||
test("clears unprefixed auth overrides when model patch changes provider", async () => {
|
||||
const store: Record<string, SessionEntry> = {
|
||||
"agent:main:main": {
|
||||
await expectProviderChangeClearsAuthOverride(
|
||||
mainAuthOverrideStore({
|
||||
sessionId: "sess-unprefixed-provider-change",
|
||||
updatedAt: 1,
|
||||
providerOverride: "anthropic",
|
||||
modelOverride: "claude-opus-4-6",
|
||||
authProfileOverride: "work",
|
||||
authProfileOverrideSource: "user",
|
||||
authProfileOverrideCompactionCount: 4,
|
||||
} as SessionEntry,
|
||||
};
|
||||
const entry = expectPatchOk(
|
||||
await runPatch({
|
||||
store,
|
||||
patch: { key: MAIN_SESSION_KEY, model: "openai/gpt-5.4" },
|
||||
loadGatewayModelCatalog: async () => [
|
||||
{ provider: "openai", id: "gpt-5.4", name: "gpt-5.4" },
|
||||
],
|
||||
}),
|
||||
);
|
||||
expect(entry.providerOverride).toBe("openai");
|
||||
expect(entry.modelOverride).toBe("gpt-5.4");
|
||||
expect(entry.authProfileOverride).toBeUndefined();
|
||||
expect(entry.authProfileOverrideSource).toBeUndefined();
|
||||
expect(entry.authProfileOverrideCompactionCount).toBeUndefined();
|
||||
});
|
||||
|
||||
test("clears provider-prefixed auth overrides when model patch changes provider", async () => {
|
||||
const store: Record<string, SessionEntry> = {
|
||||
"agent:main:main": {
|
||||
await expectProviderChangeClearsAuthOverride(
|
||||
mainAuthOverrideStore({
|
||||
sessionId: "sess-provider-change",
|
||||
updatedAt: 1,
|
||||
providerOverride: "anthropic",
|
||||
modelOverride: "claude-opus-4-6",
|
||||
authProfileOverride: "anthropic:default",
|
||||
authProfileOverrideSource: "user",
|
||||
authProfileOverrideCompactionCount: 3,
|
||||
} as SessionEntry,
|
||||
};
|
||||
const entry = expectPatchOk(
|
||||
await runPatch({
|
||||
store,
|
||||
patch: { key: MAIN_SESSION_KEY, model: "openai/gpt-5.4" },
|
||||
loadGatewayModelCatalog: async () => [
|
||||
{ provider: "openai", id: "gpt-5.4", name: "gpt-5.4" },
|
||||
],
|
||||
}),
|
||||
);
|
||||
expect(entry.providerOverride).toBe("openai");
|
||||
expect(entry.modelOverride).toBe("gpt-5.4");
|
||||
expect(entry.authProfileOverride).toBeUndefined();
|
||||
expect(entry.authProfileOverrideSource).toBeUndefined();
|
||||
expect(entry.authProfileOverrideCompactionCount).toBeUndefined();
|
||||
});
|
||||
|
||||
test("marks explicit model patches as pending live model switches", async () => {
|
||||
const store: Record<string, SessionEntry> = {
|
||||
[MAIN_SESSION_KEY]: {
|
||||
sessionId: "sess-live",
|
||||
updatedAt: 1,
|
||||
providerOverride: "openai",
|
||||
modelOverride: "gpt-5.4",
|
||||
} as SessionEntry,
|
||||
};
|
||||
const entry = expectPatchOk(
|
||||
await runPatch({
|
||||
store,
|
||||
cfg: createAllowlistedAnthropicModelCfg(),
|
||||
patch: { key: MAIN_SESSION_KEY, model: "anthropic/claude-sonnet-4-6" },
|
||||
loadGatewayModelCatalog: async () => [
|
||||
{ provider: "openai", id: "gpt-5.4", name: "gpt-5.4" },
|
||||
{ provider: "anthropic", id: "claude-sonnet-4-6", name: "claude-sonnet-4-6" },
|
||||
],
|
||||
}),
|
||||
);
|
||||
const store = mainStoreEntry({
|
||||
sessionId: "sess-live",
|
||||
providerOverride: "openai",
|
||||
modelOverride: OPENAI_GPT_ID,
|
||||
});
|
||||
const entry = await applyMainModelPatch({
|
||||
store,
|
||||
cfg: createAllowlistedAnthropicModelCfg(),
|
||||
model: ANTHROPIC_SONNET_MODEL,
|
||||
catalogRefs: [OPENAI_GPT_MODEL, ANTHROPIC_SONNET_MODEL],
|
||||
});
|
||||
|
||||
expect(entry.providerOverride).toBe("anthropic");
|
||||
expect(entry.modelOverride).toBe("claude-sonnet-4-6");
|
||||
expectModelSelection(entry, "anthropic", ANTHROPIC_SONNET_ID);
|
||||
expect(entry.liveModelSwitchPending).toBe(true);
|
||||
});
|
||||
|
||||
test("marks model reset patches as pending live model switches", async () => {
|
||||
const store: Record<string, SessionEntry> = {
|
||||
[MAIN_SESSION_KEY]: {
|
||||
sessionId: "sess-live-reset",
|
||||
updatedAt: 1,
|
||||
providerOverride: "anthropic",
|
||||
modelOverride: "claude-sonnet-4-6",
|
||||
} as SessionEntry,
|
||||
};
|
||||
const entry = expectPatchOk(
|
||||
await runPatch({
|
||||
store,
|
||||
cfg: createAllowlistedAnthropicModelCfg(),
|
||||
patch: { key: MAIN_SESSION_KEY, model: null },
|
||||
}),
|
||||
);
|
||||
const store = mainStoreEntry({
|
||||
sessionId: "sess-live-reset",
|
||||
providerOverride: "anthropic",
|
||||
modelOverride: ANTHROPIC_SONNET_ID,
|
||||
});
|
||||
const entry = await applyMainModelPatch({
|
||||
store,
|
||||
cfg: createAllowlistedAnthropicModelCfg(),
|
||||
model: null,
|
||||
});
|
||||
|
||||
expect(entry.providerOverride).toBeUndefined();
|
||||
expect(entry.modelOverride).toBeUndefined();
|
||||
expectModelSelection(entry, undefined, undefined);
|
||||
expect(entry.liveModelSwitchPending).toBe(true);
|
||||
});
|
||||
|
||||
@@ -515,12 +514,11 @@ describe("gateway sessions patch", () => {
|
||||
const entry = expectPatchOk(
|
||||
await runPatch({
|
||||
cfg: createAllowlistedAnthropicModelCfg(),
|
||||
patch: { key: MAIN_SESSION_KEY, model: "anthropic/claude-sonnet-4-6" },
|
||||
patch: { key: MAIN_SESSION_KEY, model: ANTHROPIC_SONNET_MODEL },
|
||||
loadGatewayModelCatalog: async () => catalog,
|
||||
}),
|
||||
);
|
||||
expect(entry.providerOverride).toBe("anthropic");
|
||||
expect(entry.modelOverride).toBe("claude-sonnet-4-6");
|
||||
expectModelSelection(entry, "anthropic", ANTHROPIC_SONNET_ID);
|
||||
});
|
||||
|
||||
test("sets spawnDepth for subagent sessions", async () => {
|
||||
@@ -808,7 +806,7 @@ describe("gateway sessions patch", () => {
|
||||
|
||||
test("allows target agent own model for subagent session even when missing from global allowlist", async () => {
|
||||
const cfg = makeKimiSubagentCfg({
|
||||
agentPrimaryModel: "synthetic/hf:moonshotai/Kimi-K2.5",
|
||||
agentPrimaryModel: SUBAGENT_MODEL,
|
||||
});
|
||||
|
||||
const entry = await applySubagentModelPatch(cfg);
|
||||
@@ -819,13 +817,12 @@ describe("gateway sessions patch", () => {
|
||||
|
||||
test("allows target agent subagents.model for subagent session even when missing from global allowlist", async () => {
|
||||
const cfg = makeKimiSubagentCfg({
|
||||
agentPrimaryModel: "anthropic/claude-sonnet-4-6",
|
||||
agentPrimaryModel: ANTHROPIC_SONNET_MODEL,
|
||||
agentSubagentModel: SUBAGENT_MODEL,
|
||||
});
|
||||
|
||||
const entry = await applySubagentModelPatch(cfg);
|
||||
expect(entry.providerOverride).toBe("synthetic");
|
||||
expect(entry.modelOverride).toBe("hf:moonshotai/Kimi-K2.5");
|
||||
expectModelSelection(entry, "synthetic", "hf:moonshotai/Kimi-K2.5");
|
||||
});
|
||||
|
||||
test("allows global defaults.subagents.model for subagent session even when missing from global allowlist", async () => {
|
||||
@@ -834,86 +831,57 @@ describe("gateway sessions patch", () => {
|
||||
});
|
||||
|
||||
const entry = await applySubagentModelPatch(cfg);
|
||||
expect(entry.providerOverride).toBe("synthetic");
|
||||
expect(entry.modelOverride).toBe("hf:moonshotai/Kimi-K2.5");
|
||||
expectModelSelection(entry, "synthetic", "hf:moonshotai/Kimi-K2.5");
|
||||
});
|
||||
|
||||
test("persists trailing @profile suffix as authProfileOverride on model patch", async () => {
|
||||
const entry = expectPatchOk(
|
||||
await runPatch({
|
||||
cfg: createAllowlistedAnthropicModelCfg(),
|
||||
patch: { key: MAIN_SESSION_KEY, model: "anthropic/claude-sonnet-4-6@myprofile" },
|
||||
loadGatewayModelCatalog: async () => [
|
||||
{ provider: "anthropic", id: "claude-sonnet-4-6", name: "claude-sonnet-4-6" },
|
||||
],
|
||||
}),
|
||||
);
|
||||
expect(entry.providerOverride).toBe("anthropic");
|
||||
expect(entry.modelOverride).toBe("claude-sonnet-4-6");
|
||||
expect(entry.authProfileOverride).toBe("myprofile");
|
||||
expect(entry.authProfileOverrideSource).toBe("user");
|
||||
const entry = await applyMainModelPatch({
|
||||
cfg: createAllowlistedAnthropicModelCfg(),
|
||||
model: `${ANTHROPIC_SONNET_MODEL}@myprofile`,
|
||||
catalogRefs: [ANTHROPIC_SONNET_MODEL],
|
||||
});
|
||||
expectModelSelection(entry, "anthropic", ANTHROPIC_SONNET_ID);
|
||||
expectAuthOverride(entry, { profile: "myprofile" });
|
||||
expect(entry.liveModelSwitchPending).toBe(true);
|
||||
});
|
||||
|
||||
test("marks same-model @profile patches as pending live model switches", async () => {
|
||||
const store: Record<string, SessionEntry> = {
|
||||
[MAIN_SESSION_KEY]: {
|
||||
sessionId: "sess-live-profile-only",
|
||||
updatedAt: 1,
|
||||
providerOverride: "anthropic",
|
||||
modelOverride: "claude-sonnet-4-6",
|
||||
authProfileOverride: "oldprofile",
|
||||
authProfileOverrideSource: "user",
|
||||
} as SessionEntry,
|
||||
};
|
||||
const entry = expectPatchOk(
|
||||
await runPatch({
|
||||
store,
|
||||
cfg: createAllowlistedAnthropicModelCfg(),
|
||||
patch: { key: MAIN_SESSION_KEY, model: "anthropic/claude-sonnet-4-6@newprofile" },
|
||||
loadGatewayModelCatalog: async () => [
|
||||
{ provider: "anthropic", id: "claude-sonnet-4-6", name: "claude-sonnet-4-6" },
|
||||
],
|
||||
}),
|
||||
);
|
||||
expect(entry.providerOverride).toBe("anthropic");
|
||||
expect(entry.modelOverride).toBe("claude-sonnet-4-6");
|
||||
expect(entry.authProfileOverride).toBe("newprofile");
|
||||
const store = mainStoreEntry({
|
||||
sessionId: "sess-live-profile-only",
|
||||
providerOverride: "anthropic",
|
||||
modelOverride: ANTHROPIC_SONNET_ID,
|
||||
authProfileOverride: "oldprofile",
|
||||
authProfileOverrideSource: "user",
|
||||
});
|
||||
const entry = await applyMainModelPatch({
|
||||
store,
|
||||
cfg: createAllowlistedAnthropicModelCfg(),
|
||||
model: `${ANTHROPIC_SONNET_MODEL}@newprofile`,
|
||||
catalogRefs: [ANTHROPIC_SONNET_MODEL],
|
||||
});
|
||||
expectModelSelection(entry, "anthropic", ANTHROPIC_SONNET_ID);
|
||||
expectAuthOverride(entry, { profile: "newprofile" });
|
||||
expect(entry.liveModelSwitchPending).toBe(true);
|
||||
});
|
||||
|
||||
test("does not set authProfileOverride when profile suffix is missing", async () => {
|
||||
const entry = expectPatchOk(
|
||||
await runPatch({
|
||||
cfg: createAllowlistedAnthropicModelCfg(),
|
||||
patch: { key: MAIN_SESSION_KEY, model: "anthropic/claude-sonnet-4-6" },
|
||||
loadGatewayModelCatalog: async () => [
|
||||
{ provider: "anthropic", id: "claude-sonnet-4-6", name: "claude-sonnet-4-6" },
|
||||
],
|
||||
}),
|
||||
);
|
||||
expect(entry.providerOverride).toBe("anthropic");
|
||||
expect(entry.modelOverride).toBe("claude-sonnet-4-6");
|
||||
expect(entry.authProfileOverride).toBeUndefined();
|
||||
const entry = await applyMainModelPatch({
|
||||
cfg: createAllowlistedAnthropicModelCfg(),
|
||||
model: ANTHROPIC_SONNET_MODEL,
|
||||
catalogRefs: [ANTHROPIC_SONNET_MODEL],
|
||||
});
|
||||
expectModelSelection(entry, "anthropic", ANTHROPIC_SONNET_ID);
|
||||
expectAuthOverride(entry, { profile: undefined });
|
||||
});
|
||||
|
||||
test("persists full provider:profile authProfileOverride on model patch", async () => {
|
||||
const entry = expectPatchOk(
|
||||
await runPatch({
|
||||
cfg: createAllowlistedAnthropicModelCfg(),
|
||||
patch: {
|
||||
key: MAIN_SESSION_KEY,
|
||||
model: "anthropic/claude-sonnet-4-6@openai:user@example.com",
|
||||
},
|
||||
loadGatewayModelCatalog: async () => [
|
||||
{ provider: "anthropic", id: "claude-sonnet-4-6", name: "claude-sonnet-4-6" },
|
||||
],
|
||||
}),
|
||||
);
|
||||
expect(entry.providerOverride).toBe("anthropic");
|
||||
expect(entry.modelOverride).toBe("claude-sonnet-4-6");
|
||||
expect(entry.authProfileOverride).toBe("openai:user@example.com");
|
||||
expect(entry.authProfileOverrideSource).toBe("user");
|
||||
const entry = await applyMainModelPatch({
|
||||
cfg: createAllowlistedAnthropicModelCfg(),
|
||||
model: `${ANTHROPIC_SONNET_MODEL}@openai:user@example.com`,
|
||||
catalogRefs: [ANTHROPIC_SONNET_MODEL],
|
||||
});
|
||||
expectModelSelection(entry, "anthropic", ANTHROPIC_SONNET_ID);
|
||||
expectAuthOverride(entry, { profile: "openai:user@example.com" });
|
||||
});
|
||||
|
||||
test("resolves bare allowlisted model ids before persisting @profile suffix", async () => {
|
||||
|
||||
Reference in New Issue
Block a user