mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-01 04:42:10 +03:00
fix(combo): preserve system messages during context handoff summary generation (#2865)
Integrated into release/v3.8.6.
This commit is contained in:
committed by
GitHub
parent
1442e086e4
commit
1d55f96e01
@@ -230,16 +230,36 @@ function formatMessagesForPrompt(messages: MessageLike[]): string {
|
||||
.join("\n\n");
|
||||
}
|
||||
|
||||
function selectMessagesForSummary(messages: MessageLike[], maxMessages: number): MessageLike[] {
|
||||
const recentMessages = messages.slice(-maxMessages);
|
||||
export function selectMessagesForSummary(messages: MessageLike[], maxMessages: number): MessageLike[] {
|
||||
const validMessages = messages.filter((m) => m && typeof m === "object");
|
||||
const system = validMessages.filter(
|
||||
(m) => typeof m.role === "string" && (m.role === "system" || m.role === "developer")
|
||||
);
|
||||
const nonSystem = validMessages.filter(
|
||||
(m) => typeof m.role !== "string" || (m.role !== "system" && m.role !== "developer")
|
||||
);
|
||||
|
||||
const recentMessages = [...system, ...nonSystem.slice(-maxMessages)];
|
||||
let working = [...recentMessages];
|
||||
|
||||
while (working.length > 1) {
|
||||
while (working.length > system.length + 1) {
|
||||
const history = formatMessagesForPrompt(working);
|
||||
if (estimateTokens(history) <= MAX_HISTORY_TOKENS_FOR_SUMMARY) {
|
||||
return working;
|
||||
}
|
||||
working = working.slice(1);
|
||||
working = [...system, ...working.slice(system.length + 1)];
|
||||
}
|
||||
|
||||
const fallbackHistory = formatMessagesForPrompt(working);
|
||||
if (estimateTokens(fallbackHistory) > MAX_HISTORY_TOKENS_FOR_SUMMARY) {
|
||||
// If there are system messages, return them so the caller can still produce context.
|
||||
// If there are no system messages (system=[]), fall back to the single most-recent
|
||||
// non-system message rather than returning [] which would silently drop the handoff.
|
||||
if (system.length > 0) {
|
||||
return system;
|
||||
}
|
||||
const lastNonSystem = nonSystem[nonSystem.length - 1];
|
||||
return lastNonSystem ? [lastNonSystem] : [];
|
||||
}
|
||||
|
||||
return working;
|
||||
|
||||
@@ -368,3 +368,55 @@ test("context handoff DB module upserts and deletes active handoffs", () => {
|
||||
handoffDb.deleteHandoff("sess-db", "relay-combo");
|
||||
assert.equal(handoffDb.getHandoff("sess-db", "relay-combo"), null);
|
||||
});
|
||||
|
||||
test("selectMessagesForSummary filters falsy values and preserves system/developer messages", () => {
|
||||
const messages: (contextHandoff.MessageLike | null | undefined | false)[] = [
|
||||
null,
|
||||
undefined,
|
||||
{ role: "system", content: "System 1" },
|
||||
false,
|
||||
{ role: "user", content: "User 1" },
|
||||
{ role: "developer", content: "Dev 1" },
|
||||
{ role: "assistant", content: "Assistant 1" },
|
||||
{ role: "user", content: "User 2" },
|
||||
];
|
||||
|
||||
const selected = contextHandoff.selectMessagesForSummary(
|
||||
messages as contextHandoff.MessageLike[],
|
||||
2
|
||||
);
|
||||
|
||||
assert.equal(selected.length, 4);
|
||||
assert.equal(selected[0].role, "system");
|
||||
assert.equal(selected[1].role, "developer");
|
||||
assert.equal(selected[2].role, "assistant");
|
||||
assert.equal(selected[3].role, "user");
|
||||
assert.equal(selected[3].content, "User 2");
|
||||
});
|
||||
|
||||
test("selectMessagesForSummary with no system messages and oversized single remaining message still produces non-empty selection", () => {
|
||||
// Build a single very large non-system message that exceeds MAX_HISTORY_TOKENS_FOR_SUMMARY
|
||||
// (token estimator is ~4 chars/token, so 8000 tokens ≈ 32000 chars).
|
||||
const hugeContent = "x".repeat(40000);
|
||||
const messages: contextHandoff.MessageLike[] = [
|
||||
{ role: "user", content: "first message" },
|
||||
{ role: "assistant", content: hugeContent },
|
||||
];
|
||||
|
||||
const selected = contextHandoff.selectMessagesForSummary(messages, 10);
|
||||
|
||||
// The function must return at least one message rather than [] so the handoff is not silently dropped.
|
||||
assert.ok(selected.length > 0, "expected at least one message to be selected");
|
||||
|
||||
// formatMessagesForPrompt on the result must produce a non-empty string
|
||||
// (guards the regression: previously returned [] → empty historyText → handoff skipped).
|
||||
const historyText = selected
|
||||
.map((m, i) => {
|
||||
const role = typeof m.role === "string" ? m.role : "unknown";
|
||||
const content = typeof m.content === "string" ? m.content.trim() : "";
|
||||
return content ? `[${i + 1}] ${role.toUpperCase()}:\n${content}` : "";
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join("\n\n");
|
||||
assert.ok(historyText.length > 0, "historyText must be non-empty so the handoff is generated");
|
||||
});
|
||||
|
||||
@@ -154,7 +154,7 @@ test("#2348 .dockerignore keeps every doc the in-product viewer needs", () => {
|
||||
|
||||
test("#2348 .dockerignore still excludes the heavy i18n tree", () => {
|
||||
const parsed = parseDockerignore(fs.readFileSync(DOCKERIGNORE, "utf8"));
|
||||
const heavy = "docs/i18n/pt-BR/docs/AUTO-COMBO.md";
|
||||
const heavy = "docs/i18n/pt-BR/docs/routing/AUTO-COMBO.md";
|
||||
assert.ok(
|
||||
isIgnored(heavy, parsed),
|
||||
`${heavy} should be excluded from Docker context but is not — image size will balloon`
|
||||
|
||||
Reference in New Issue
Block a user