Files
diegosouzapw--omniroute/tests/unit/mistral-trailing-assistant.test.ts
2026-07-13 13:39:12 +08:00

85 lines
3.6 KiB
TypeScript

/**
* Regression test for #3396: Mistral returns 400 when the last message is
* `role: "assistant"` with plain text content.
*
* `stripTrailingAssistantOrphanToolUse` only removed tool_use blocks — it left
* trailing text-only assistant messages intact. Mistral (and providers sharing
* the same constraint) reject such requests with:
* "400: Expected last role User or Tool (or Assistant with prefix True)
* for serving but got assistant"
*
* The fix adds `stripTrailingAssistantForProvider(messages, provider)` which
* also drops a trailing text-only assistant message for providers that require
* user-last format (e.g. "mistral").
*/
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import { stripTrailingAssistantOrphanToolUse } from "../../open-sse/services/contextManager.ts";
import { stripTrailingAssistantForProvider } from "../../open-sse/services/contextManager.ts";
const user = (content: string) => ({ role: "user", content });
const assistant = (content: string) => ({ role: "assistant", content });
const assistantWithToolUse = () => ({
role: "assistant",
content: [{ type: "tool_use", id: "t1", name: "fn", input: {} }],
});
describe("stripTrailingAssistantForProvider (#3396)", () => {
it("strips trailing text-only assistant message for mistral", () => {
const msgs = [user("hi"), assistant("hello from model")];
const result = stripTrailingAssistantForProvider(msgs, "mistral");
assert.strictEqual(result.length, 1);
assert.strictEqual(result[0].role, "user");
});
it("strips trailing assistant with array-string content for mistral", () => {
const msgs = [
user("hi"),
{ role: "assistant", content: [{ type: "text", text: "response" }] },
];
const result = stripTrailingAssistantForProvider(msgs, "mistral");
assert.strictEqual(result.length, 1);
assert.strictEqual(result[0].role, "user");
});
it("does NOT strip trailing tool_use assistant (stripTrailingOrphan handles that)", () => {
const msgs = [user("hi"), assistantWithToolUse()];
const result = stripTrailingAssistantForProvider(msgs, "mistral");
// tool_use trailing is already handled by stripTrailingAssistantOrphanToolUse;
// this function should not double-strip or corrupt tool-use state
assert.strictEqual(result.length, 2);
});
it("does NOT strip trailing text assistant for non-mistral providers", () => {
const msgs = [user("hi"), assistant("response")];
const result = stripTrailingAssistantForProvider(msgs, "openai");
assert.strictEqual(result.length, 2);
});
it("does NOT strip trailing text assistant for anthropic/claude", () => {
const msgs = [user("hi"), assistant("continue from here")];
const result = stripTrailingAssistantForProvider(msgs, "claude");
assert.strictEqual(result.length, 2);
});
it("returns messages unchanged when last message is user", () => {
const msgs = [assistant("a"), user("b")];
const result = stripTrailingAssistantForProvider(msgs, "mistral");
assert.strictEqual(result.length, 2);
assert.strictEqual(result[1].role, "user");
});
it("returns empty array unchanged", () => {
const result = stripTrailingAssistantForProvider([], "mistral");
assert.deepStrictEqual(result, []);
});
it("existing stripTrailingAssistantOrphanToolUse still works after change", () => {
const msgs = [user("hi"), assistantWithToolUse()];
const result = stripTrailingAssistantOrphanToolUse(msgs);
// tool_use block removed; empty assistant → dropped
assert.strictEqual(result.length, 1);
assert.strictEqual(result[0].role, "user");
});
});