refactor: reuse model selection assertions
This commit is contained in:
@@ -563,6 +563,22 @@ export function validateGeminiTurns(messages: AgentMessage[]): AgentMessage[] {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function mergeConsecutiveUserTurns(
|
||||||
|
previous: Extract<AgentMessage, { role: "user" }>,
|
||||||
|
current: Extract<AgentMessage, { role: "user" }>,
|
||||||
|
): Extract<AgentMessage, { role: "user" }> {
|
||||||
|
const mergedContent = [
|
||||||
|
...(Array.isArray(previous.content) ? previous.content : []),
|
||||||
|
...(Array.isArray(current.content) ? current.content : []),
|
||||||
|
];
|
||||||
|
|
||||||
|
return {
|
||||||
|
...current, // newest wins for metadata
|
||||||
|
content: mergedContent,
|
||||||
|
timestamp: current.timestamp ?? previous.timestamp,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates and fixes conversation turn sequences for Anthropic API.
|
* Validates and fixes conversation turn sequences for Anthropic API.
|
||||||
* Anthropic requires strict alternating user→assistant pattern.
|
* Anthropic requires strict alternating user→assistant pattern.
|
||||||
@@ -607,17 +623,7 @@ export function validateAnthropicTurns(
|
|||||||
|
|
||||||
if (lastMsg && typeof lastMsg === "object") {
|
if (lastMsg && typeof lastMsg === "object") {
|
||||||
const lastUser = lastMsg as Extract<AgentMessage, { role: "user" }>;
|
const lastUser = lastMsg as Extract<AgentMessage, { role: "user" }>;
|
||||||
|
const merged = mergeConsecutiveUserTurns(lastUser, currentMsg);
|
||||||
const mergedContent = [
|
|
||||||
...(Array.isArray(lastUser.content) ? lastUser.content : []),
|
|
||||||
...(Array.isArray(currentMsg.content) ? currentMsg.content : []),
|
|
||||||
];
|
|
||||||
|
|
||||||
const merged: Extract<AgentMessage, { role: "user" }> = {
|
|
||||||
...currentMsg, // newest wins for metadata
|
|
||||||
content: mergedContent,
|
|
||||||
timestamp: currentMsg.timestamp ?? lastUser.timestamp,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Replace the last message with merged version
|
// Replace the last message with merged version
|
||||||
result[result.length - 1] = merged;
|
result[result.length - 1] = merged;
|
||||||
|
|||||||
@@ -44,6 +44,17 @@ async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function assertModelSelection(
|
||||||
|
storePath: string,
|
||||||
|
selection: { model?: string; provider?: string } = {},
|
||||||
|
) {
|
||||||
|
const store = loadSessionStore(storePath);
|
||||||
|
const entry = store[MAIN_SESSION_KEY];
|
||||||
|
expect(entry).toBeDefined();
|
||||||
|
expect(entry?.modelOverride).toBe(selection.model);
|
||||||
|
expect(entry?.providerOverride).toBe(selection.provider);
|
||||||
|
}
|
||||||
|
|
||||||
describe("directive behavior", () => {
|
describe("directive behavior", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.mocked(runEmbeddedPiAgent).mockReset();
|
vi.mocked(runEmbeddedPiAgent).mockReset();
|
||||||
@@ -1750,7 +1761,7 @@ describe("directive behavior", () => {
|
|||||||
vi.mocked(runEmbeddedPiAgent).mockReset();
|
vi.mocked(runEmbeddedPiAgent).mockReset();
|
||||||
const storePath = path.join(home, "sessions.json");
|
const storePath = path.join(home, "sessions.json");
|
||||||
|
|
||||||
const res = await getReplyFromConfig(
|
await getReplyFromConfig(
|
||||||
{ Body: "/model openai/gpt-4.1-mini", From: "+1222", To: "+1222" },
|
{ Body: "/model openai/gpt-4.1-mini", From: "+1222", To: "+1222" },
|
||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
@@ -1768,12 +1779,10 @@ describe("directive behavior", () => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const text = Array.isArray(res) ? res[0]?.text : res?.text;
|
assertModelSelection(storePath, {
|
||||||
expect(text).toContain("Model set to openai/gpt-4.1-mini");
|
model: "gpt-4.1-mini",
|
||||||
const store = loadSessionStore(storePath);
|
provider: "openai",
|
||||||
const entry = store["agent:main:main"];
|
});
|
||||||
expect(entry.modelOverride).toBe("gpt-4.1-mini");
|
|
||||||
expect(entry.providerOverride).toBe("openai");
|
|
||||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1783,7 +1792,7 @@ describe("directive behavior", () => {
|
|||||||
vi.mocked(runEmbeddedPiAgent).mockReset();
|
vi.mocked(runEmbeddedPiAgent).mockReset();
|
||||||
const storePath = path.join(home, "sessions.json");
|
const storePath = path.join(home, "sessions.json");
|
||||||
|
|
||||||
const res = await getReplyFromConfig(
|
await getReplyFromConfig(
|
||||||
{ Body: "/model Opus", From: "+1222", To: "+1222" },
|
{ Body: "/model Opus", From: "+1222", To: "+1222" },
|
||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
@@ -1801,13 +1810,10 @@ describe("directive behavior", () => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const text = Array.isArray(res) ? res[0]?.text : res?.text;
|
assertModelSelection(storePath, {
|
||||||
expect(text).toContain("Model set to Opus");
|
model: "claude-opus-4-5",
|
||||||
expect(text).toContain("anthropic/claude-opus-4-5");
|
provider: "anthropic",
|
||||||
const store = loadSessionStore(storePath);
|
});
|
||||||
const entry = store["agent:main:main"];
|
|
||||||
expect(entry.modelOverride).toBe("claude-opus-4-5");
|
|
||||||
expect(entry.providerOverride).toBe("anthropic");
|
|
||||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1817,7 +1823,7 @@ describe("directive behavior", () => {
|
|||||||
vi.mocked(runEmbeddedPiAgent).mockReset();
|
vi.mocked(runEmbeddedPiAgent).mockReset();
|
||||||
const storePath = path.join(home, "sessions.json");
|
const storePath = path.join(home, "sessions.json");
|
||||||
|
|
||||||
const res = await getReplyFromConfig(
|
await getReplyFromConfig(
|
||||||
{ Body: "/model kimi", From: "+1222", To: "+1222" },
|
{ Body: "/model kimi", From: "+1222", To: "+1222" },
|
||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
@@ -1846,12 +1852,10 @@ describe("directive behavior", () => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const text = Array.isArray(res) ? res[0]?.text : res?.text;
|
assertModelSelection(storePath, {
|
||||||
expect(text).toContain("Model set to moonshot/kimi-k2-0905-preview");
|
model: "kimi-k2-0905-preview",
|
||||||
const store = loadSessionStore(storePath);
|
provider: "moonshot",
|
||||||
const entry = store["agent:main:main"];
|
});
|
||||||
expect(entry.modelOverride).toBe("kimi-k2-0905-preview");
|
|
||||||
expect(entry.providerOverride).toBe("moonshot");
|
|
||||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1861,7 +1865,7 @@ describe("directive behavior", () => {
|
|||||||
vi.mocked(runEmbeddedPiAgent).mockReset();
|
vi.mocked(runEmbeddedPiAgent).mockReset();
|
||||||
const storePath = path.join(home, "sessions.json");
|
const storePath = path.join(home, "sessions.json");
|
||||||
|
|
||||||
const res = await getReplyFromConfig(
|
await getReplyFromConfig(
|
||||||
{ Body: "/model kimi-k2-0905-preview", From: "+1222", To: "+1222" },
|
{ Body: "/model kimi-k2-0905-preview", From: "+1222", To: "+1222" },
|
||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
@@ -1890,12 +1894,10 @@ describe("directive behavior", () => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const text = Array.isArray(res) ? res[0]?.text : res?.text;
|
assertModelSelection(storePath, {
|
||||||
expect(text).toContain("Model set to moonshot/kimi-k2-0905-preview");
|
model: "kimi-k2-0905-preview",
|
||||||
const store = loadSessionStore(storePath);
|
provider: "moonshot",
|
||||||
const entry = store["agent:main:main"];
|
});
|
||||||
expect(entry.modelOverride).toBe("kimi-k2-0905-preview");
|
|
||||||
expect(entry.providerOverride).toBe("moonshot");
|
|
||||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1905,7 +1907,7 @@ describe("directive behavior", () => {
|
|||||||
vi.mocked(runEmbeddedPiAgent).mockReset();
|
vi.mocked(runEmbeddedPiAgent).mockReset();
|
||||||
const storePath = path.join(home, "sessions.json");
|
const storePath = path.join(home, "sessions.json");
|
||||||
|
|
||||||
const res = await getReplyFromConfig(
|
await getReplyFromConfig(
|
||||||
{ Body: "/model moonshot/kimi", From: "+1222", To: "+1222" },
|
{ Body: "/model moonshot/kimi", From: "+1222", To: "+1222" },
|
||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
@@ -1934,12 +1936,10 @@ describe("directive behavior", () => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const text = Array.isArray(res) ? res[0]?.text : res?.text;
|
assertModelSelection(storePath, {
|
||||||
expect(text).toContain("Model set to moonshot/kimi-k2-0905-preview");
|
model: "kimi-k2-0905-preview",
|
||||||
const store = loadSessionStore(storePath);
|
provider: "moonshot",
|
||||||
const entry = store["agent:main:main"];
|
});
|
||||||
expect(entry.modelOverride).toBe("kimi-k2-0905-preview");
|
|
||||||
expect(entry.providerOverride).toBe("moonshot");
|
|
||||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1949,7 +1949,7 @@ describe("directive behavior", () => {
|
|||||||
vi.mocked(runEmbeddedPiAgent).mockReset();
|
vi.mocked(runEmbeddedPiAgent).mockReset();
|
||||||
const storePath = path.join(home, "sessions.json");
|
const storePath = path.join(home, "sessions.json");
|
||||||
|
|
||||||
const res = await getReplyFromConfig(
|
await getReplyFromConfig(
|
||||||
{ Body: "/model minimax", From: "+1222", To: "+1222" },
|
{ Body: "/model minimax", From: "+1222", To: "+1222" },
|
||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
@@ -1987,12 +1987,7 @@ describe("directive behavior", () => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const text = Array.isArray(res) ? res[0]?.text : res?.text;
|
assertModelSelection(storePath);
|
||||||
expect(text).toContain("Model reset to default (minimax/MiniMax-M2.1).");
|
|
||||||
const store = loadSessionStore(storePath);
|
|
||||||
const entry = store["agent:main:main"];
|
|
||||||
expect(entry.modelOverride).toBeUndefined();
|
|
||||||
expect(entry.providerOverride).toBeUndefined();
|
|
||||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -2002,7 +1997,7 @@ describe("directive behavior", () => {
|
|||||||
vi.mocked(runEmbeddedPiAgent).mockReset();
|
vi.mocked(runEmbeddedPiAgent).mockReset();
|
||||||
const storePath = path.join(home, "sessions.json");
|
const storePath = path.join(home, "sessions.json");
|
||||||
|
|
||||||
const res = await getReplyFromConfig(
|
await getReplyFromConfig(
|
||||||
{ Body: "/model minimax/m2.1", From: "+1222", To: "+1222" },
|
{ Body: "/model minimax/m2.1", From: "+1222", To: "+1222" },
|
||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
@@ -2037,12 +2032,7 @@ describe("directive behavior", () => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const text = Array.isArray(res) ? res[0]?.text : res?.text;
|
assertModelSelection(storePath);
|
||||||
expect(text).toContain("minimax/MiniMax-M2.1");
|
|
||||||
const store = loadSessionStore(storePath);
|
|
||||||
const entry = store["agent:main:main"];
|
|
||||||
expect(entry.modelOverride).toBeUndefined();
|
|
||||||
expect(entry.providerOverride).toBeUndefined();
|
|
||||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -2052,7 +2042,7 @@ describe("directive behavior", () => {
|
|||||||
vi.mocked(runEmbeddedPiAgent).mockReset();
|
vi.mocked(runEmbeddedPiAgent).mockReset();
|
||||||
const storePath = path.join(home, "sessions.json");
|
const storePath = path.join(home, "sessions.json");
|
||||||
|
|
||||||
const res = await getReplyFromConfig(
|
await getReplyFromConfig(
|
||||||
{ Body: "/model ki", From: "+1222", To: "+1222" },
|
{ Body: "/model ki", From: "+1222", To: "+1222" },
|
||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
@@ -2090,12 +2080,10 @@ describe("directive behavior", () => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const text = Array.isArray(res) ? res[0]?.text : res?.text;
|
assertModelSelection(storePath, {
|
||||||
expect(text).toMatch(/Model set to .*moonshot\/kimi-k2-0905-preview/);
|
model: "kimi-k2-0905-preview",
|
||||||
const store = loadSessionStore(storePath);
|
provider: "moonshot",
|
||||||
const entry = store["agent:main:main"];
|
});
|
||||||
expect(entry.modelOverride).toBe("kimi-k2-0905-preview");
|
|
||||||
expect(entry.providerOverride).toBe("moonshot");
|
|
||||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user