mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-31 04:12:10 +03:00
fix: extract system role messages in semantic passthrough path + add test
This commit is contained in:
@@ -1242,6 +1242,41 @@ function isCopilotClient(
|
||||
return false;
|
||||
}
|
||||
|
||||
export function extractSystemRoleMessages(payload: Record<string, unknown>): void {
|
||||
if (!Array.isArray(payload.messages)) return;
|
||||
const messages = payload.messages as Array<{ role?: unknown; content?: unknown }>;
|
||||
const systemMessages = messages.filter(
|
||||
(m) => typeof m.role === "string" && m.role.toLowerCase() === "system"
|
||||
);
|
||||
if (systemMessages.length === 0) return;
|
||||
|
||||
const extraBlocks: Array<Record<string, unknown>> = [];
|
||||
for (const sm of systemMessages) {
|
||||
if (typeof sm.content === "string" && sm.content.length > 0) {
|
||||
extraBlocks.push({ type: "text", text: sm.content });
|
||||
} else if (Array.isArray(sm.content)) {
|
||||
for (const block of sm.content as Array<Record<string, unknown>>) {
|
||||
if (block?.type === "text" && typeof block.text === "string" && block.text.length > 0) {
|
||||
extraBlocks.push({ ...block });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (extraBlocks.length > 0) {
|
||||
const existingSystem = payload.system;
|
||||
if (typeof existingSystem === "string" && existingSystem.length > 0) {
|
||||
payload.system = [{ type: "text", text: existingSystem }, ...extraBlocks];
|
||||
} else if (Array.isArray(existingSystem)) {
|
||||
payload.system = [...(existingSystem as Array<Record<string, unknown>>), ...extraBlocks];
|
||||
} else {
|
||||
payload.system = extraBlocks;
|
||||
}
|
||||
}
|
||||
payload.messages = messages.filter(
|
||||
(m) => typeof m.role !== "string" || m.role.toLowerCase() !== "system"
|
||||
);
|
||||
}
|
||||
|
||||
export async function handleChatCore({
|
||||
body,
|
||||
modelInfo,
|
||||
@@ -2577,45 +2612,6 @@ export async function handleChatCore({
|
||||
content?: unknown;
|
||||
};
|
||||
|
||||
/**
|
||||
* Lightweight extraction: only lifts role:"system" messages to the top-level
|
||||
* `system` parameter. Unlike normalizeClaudeUpstreamMessages, this does NOT
|
||||
* convert file/document blocks, drop unknown types, or change tool history.
|
||||
* Used in the semantic passthrough path where Claude Code's native payload
|
||||
* structure must be preserved — only memory injection (which prepends a
|
||||
* system message) needs this correction.
|
||||
*/
|
||||
const extractSystemRoleMessages = (payload: Record<string, unknown>) => {
|
||||
if (!Array.isArray(payload.messages)) return;
|
||||
const messages = payload.messages as ClaudeMessage[];
|
||||
const systemMessages = messages.filter((m) => m.role === "system");
|
||||
if (systemMessages.length === 0) return;
|
||||
|
||||
const extraBlocks: ClaudeContentBlock[] = [];
|
||||
for (const sm of systemMessages) {
|
||||
if (typeof sm.content === "string" && sm.content.length > 0) {
|
||||
extraBlocks.push({ type: "text", text: sm.content });
|
||||
} else if (Array.isArray(sm.content)) {
|
||||
for (const block of sm.content as ClaudeContentBlock[]) {
|
||||
if (block?.type === "text" && typeof block.text === "string" && block.text.length > 0) {
|
||||
extraBlocks.push(block);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (extraBlocks.length > 0) {
|
||||
const existingSystem = payload.system;
|
||||
if (typeof existingSystem === "string" && existingSystem.length > 0) {
|
||||
payload.system = [{ type: "text", text: existingSystem }, ...extraBlocks];
|
||||
} else if (Array.isArray(existingSystem)) {
|
||||
payload.system = [...(existingSystem as ClaudeContentBlock[]), ...extraBlocks];
|
||||
} else {
|
||||
payload.system = extraBlocks;
|
||||
}
|
||||
}
|
||||
payload.messages = messages.filter((m) => m.role !== "system");
|
||||
};
|
||||
|
||||
const normalizeClaudeUpstreamMessages = (
|
||||
payload: Record<string, unknown>,
|
||||
options?: { preserveToolResultBlocks?: boolean }
|
||||
|
||||
119
tests/unit/system-role-extraction.test.ts
Normal file
119
tests/unit/system-role-extraction.test.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { extractSystemRoleMessages } from "../../open-sse/handlers/chatCore.ts";
|
||||
|
||||
test("extractSystemRoleMessages moves role=system to top-level system", () => {
|
||||
const payload = {
|
||||
messages: [
|
||||
{ role: "system", content: "Memory context: foo" },
|
||||
{ role: "user", content: "hello" },
|
||||
{ role: "assistant", content: "hi" },
|
||||
],
|
||||
};
|
||||
extractSystemRoleMessages(payload);
|
||||
assert.equal(payload.messages.length, 2);
|
||||
assert.equal(payload.messages[0].role, "user");
|
||||
assert.deepEqual(payload.system, [{ type: "text", text: "Memory context: foo" }]);
|
||||
});
|
||||
|
||||
test("extractSystemRoleMessages merges with existing top-level system string", () => {
|
||||
const payload = {
|
||||
system: "You are Claude.",
|
||||
messages: [
|
||||
{ role: "system", content: "Memory context: bar" },
|
||||
{ role: "user", content: "hello" },
|
||||
],
|
||||
};
|
||||
extractSystemRoleMessages(payload);
|
||||
assert.equal(payload.messages.length, 1);
|
||||
assert.deepEqual(payload.system, [
|
||||
{ type: "text", text: "You are Claude." },
|
||||
{ type: "text", text: "Memory context: bar" },
|
||||
]);
|
||||
});
|
||||
|
||||
test("extractSystemRoleMessages merges with existing top-level system array", () => {
|
||||
const payload = {
|
||||
system: [{ type: "text", text: "Existing system" }],
|
||||
messages: [
|
||||
{ role: "system", content: "Memory context: baz" },
|
||||
{ role: "user", content: "hello" },
|
||||
],
|
||||
};
|
||||
extractSystemRoleMessages(payload);
|
||||
assert.equal(payload.messages.length, 1);
|
||||
assert.deepEqual(payload.system, [
|
||||
{ type: "text", text: "Existing system" },
|
||||
{ type: "text", text: "Memory context: baz" },
|
||||
]);
|
||||
});
|
||||
|
||||
test("extractSystemRoleMessages does nothing when no system role messages", () => {
|
||||
const payload = {
|
||||
messages: [
|
||||
{ role: "user", content: "hello" },
|
||||
{ role: "assistant", content: "hi" },
|
||||
],
|
||||
};
|
||||
extractSystemRoleMessages(payload);
|
||||
assert.equal(payload.messages.length, 2);
|
||||
assert.equal(payload.system, undefined);
|
||||
});
|
||||
|
||||
test("extractSystemRoleMessages handles non-array messages gracefully", () => {
|
||||
const payload = { messages: "not-an-array" };
|
||||
extractSystemRoleMessages(payload);
|
||||
assert.equal(payload.messages, "not-an-array");
|
||||
});
|
||||
|
||||
test("extractSystemRoleMessages handles empty messages array", () => {
|
||||
const payload = { messages: [] };
|
||||
extractSystemRoleMessages(payload);
|
||||
assert.equal(payload.messages.length, 0);
|
||||
});
|
||||
|
||||
test("extractSystemRoleMessages handles case-insensitive role System", () => {
|
||||
const payload = {
|
||||
messages: [
|
||||
{ role: "System", content: "Memory context: caps" },
|
||||
{ role: "user", content: "hello" },
|
||||
],
|
||||
};
|
||||
extractSystemRoleMessages(payload);
|
||||
assert.equal(payload.messages.length, 1);
|
||||
assert.deepEqual(payload.system, [{ type: "text", text: "Memory context: caps" }]);
|
||||
});
|
||||
|
||||
test("extractSystemRoleMessages drops empty text content from system messages", () => {
|
||||
const payload = {
|
||||
messages: [
|
||||
{ role: "system", content: "" },
|
||||
{ role: "system", content: "valid" },
|
||||
{ role: "user", content: "hello" },
|
||||
],
|
||||
};
|
||||
extractSystemRoleMessages(payload);
|
||||
assert.equal(payload.messages.length, 1);
|
||||
assert.deepEqual(payload.system, [{ type: "text", text: "valid" }]);
|
||||
});
|
||||
|
||||
test("extractSystemRoleMessages handles system messages with array content", () => {
|
||||
const payload = {
|
||||
messages: [
|
||||
{
|
||||
role: "system",
|
||||
content: [
|
||||
{ type: "text", text: "Block 1" },
|
||||
{ type: "text", text: "Block 2" },
|
||||
],
|
||||
},
|
||||
{ role: "user", content: "hello" },
|
||||
],
|
||||
};
|
||||
extractSystemRoleMessages(payload);
|
||||
assert.equal(payload.messages.length, 1);
|
||||
assert.deepEqual(payload.system, [
|
||||
{ type: "text", text: "Block 1" },
|
||||
{ type: "text", text: "Block 2" },
|
||||
]);
|
||||
});
|
||||
Reference in New Issue
Block a user