feat: unify provider history context
This commit is contained in:
@@ -81,8 +81,10 @@ export async function tryFastAbortFromMessage(params: {
|
||||
sessionKey: targetKey ?? ctx.SessionKey ?? "",
|
||||
config: cfg,
|
||||
});
|
||||
// Use RawBody for abort detection (clean message without structural context).
|
||||
const raw = stripStructuralPrefixes(ctx.RawBody ?? ctx.Body ?? "");
|
||||
// Use RawBody/CommandBody for abort detection (clean message without structural context).
|
||||
const raw = stripStructuralPrefixes(
|
||||
ctx.CommandBody ?? ctx.RawBody ?? ctx.Body ?? "",
|
||||
);
|
||||
const isGroup = ctx.ChatType?.trim().toLowerCase() === "group";
|
||||
const stripped = isGroup ? stripMentions(raw, ctx, cfg, agentId) : raw;
|
||||
const normalized = normalizeCommandBody(stripped);
|
||||
|
||||
@@ -900,7 +900,7 @@ export async function handleCommands(params: {
|
||||
await waitForEmbeddedPiRunEnd(sessionId, 15_000);
|
||||
}
|
||||
const customInstructions = extractCompactInstructions({
|
||||
rawBody: ctx.Body,
|
||||
rawBody: ctx.CommandBody ?? ctx.RawBody ?? ctx.Body,
|
||||
ctx,
|
||||
cfg,
|
||||
agentId: params.agentId,
|
||||
|
||||
63
src/auto-reply/reply/history.test.ts
Normal file
63
src/auto-reply/reply/history.test.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { CURRENT_MESSAGE_MARKER } from "./mentions.js";
|
||||
import {
|
||||
HISTORY_CONTEXT_MARKER,
|
||||
appendHistoryEntry,
|
||||
buildHistoryContext,
|
||||
buildHistoryContextFromEntries,
|
||||
} from "./history.js";
|
||||
|
||||
describe("history helpers", () => {
|
||||
it("returns current message when history is empty", () => {
|
||||
const result = buildHistoryContext({
|
||||
historyText: " ",
|
||||
currentMessage: "hello",
|
||||
});
|
||||
expect(result).toBe("hello");
|
||||
});
|
||||
|
||||
it("wraps history entries and excludes current by default", () => {
|
||||
const result = buildHistoryContextFromEntries({
|
||||
entries: [
|
||||
{ sender: "A", body: "one" },
|
||||
{ sender: "B", body: "two" },
|
||||
],
|
||||
currentMessage: "current",
|
||||
formatEntry: (entry) => `${entry.sender}: ${entry.body}`,
|
||||
});
|
||||
|
||||
expect(result).toContain(HISTORY_CONTEXT_MARKER);
|
||||
expect(result).toContain("A: one");
|
||||
expect(result).not.toContain("B: two");
|
||||
expect(result).toContain(CURRENT_MESSAGE_MARKER);
|
||||
expect(result).toContain("current");
|
||||
});
|
||||
|
||||
it("trims history to configured limit", () => {
|
||||
const historyMap = new Map<string, { sender: string; body: string }[]>();
|
||||
|
||||
appendHistoryEntry({
|
||||
historyMap,
|
||||
historyKey: "room",
|
||||
limit: 2,
|
||||
entry: { sender: "A", body: "one" },
|
||||
});
|
||||
appendHistoryEntry({
|
||||
historyMap,
|
||||
historyKey: "room",
|
||||
limit: 2,
|
||||
entry: { sender: "B", body: "two" },
|
||||
});
|
||||
appendHistoryEntry({
|
||||
historyMap,
|
||||
historyKey: "room",
|
||||
limit: 2,
|
||||
entry: { sender: "C", body: "three" },
|
||||
});
|
||||
|
||||
expect(historyMap.get("room")?.map((entry) => entry.body)).toEqual([
|
||||
"two",
|
||||
"three",
|
||||
]);
|
||||
});
|
||||
});
|
||||
64
src/auto-reply/reply/history.ts
Normal file
64
src/auto-reply/reply/history.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { CURRENT_MESSAGE_MARKER } from "./mentions.js";
|
||||
|
||||
export const HISTORY_CONTEXT_MARKER =
|
||||
"[Chat messages since your last reply - for context]";
|
||||
export const DEFAULT_GROUP_HISTORY_LIMIT = 50;
|
||||
|
||||
export type HistoryEntry = {
|
||||
sender: string;
|
||||
body: string;
|
||||
timestamp?: number;
|
||||
messageId?: string;
|
||||
};
|
||||
|
||||
export function buildHistoryContext(params: {
|
||||
historyText: string;
|
||||
currentMessage: string;
|
||||
lineBreak?: string;
|
||||
}): string {
|
||||
const { historyText, currentMessage } = params;
|
||||
const lineBreak = params.lineBreak ?? "\n";
|
||||
if (!historyText.trim()) return currentMessage;
|
||||
return [
|
||||
HISTORY_CONTEXT_MARKER,
|
||||
historyText,
|
||||
"",
|
||||
CURRENT_MESSAGE_MARKER,
|
||||
currentMessage,
|
||||
].join(lineBreak);
|
||||
}
|
||||
|
||||
export function appendHistoryEntry(params: {
|
||||
historyMap: Map<string, HistoryEntry[]>;
|
||||
historyKey: string;
|
||||
entry: HistoryEntry;
|
||||
limit: number;
|
||||
}): HistoryEntry[] {
|
||||
const { historyMap, historyKey, entry } = params;
|
||||
if (params.limit <= 0) return [];
|
||||
const history = historyMap.get(historyKey) ?? [];
|
||||
history.push(entry);
|
||||
while (history.length > params.limit) history.shift();
|
||||
historyMap.set(historyKey, history);
|
||||
return history;
|
||||
}
|
||||
|
||||
export function buildHistoryContextFromEntries(params: {
|
||||
entries: HistoryEntry[];
|
||||
currentMessage: string;
|
||||
formatEntry: (entry: HistoryEntry) => string;
|
||||
lineBreak?: string;
|
||||
excludeLast?: boolean;
|
||||
}): string {
|
||||
const lineBreak = params.lineBreak ?? "\n";
|
||||
const entries = params.excludeLast === false
|
||||
? params.entries
|
||||
: params.entries.slice(0, -1);
|
||||
if (entries.length === 0) return params.currentMessage;
|
||||
const historyText = entries.map(params.formatEntry).join(lineBreak);
|
||||
return buildHistoryContext({
|
||||
historyText,
|
||||
currentMessage: params.currentMessage,
|
||||
lineBreak,
|
||||
});
|
||||
}
|
||||
@@ -136,15 +136,15 @@ export async function initSessionState(params: {
|
||||
resolveGroupSessionKey(sessionCtxForState) ?? undefined;
|
||||
const isGroup =
|
||||
ctx.ChatType?.trim().toLowerCase() === "group" || Boolean(groupResolution);
|
||||
// Prefer RawBody (clean message) for command detection; fall back to Body
|
||||
// which may contain structural context (history, sender labels).
|
||||
const commandSource = ctx.RawBody ?? ctx.Body ?? "";
|
||||
// Prefer CommandBody/RawBody (clean message) for command detection; fall back
|
||||
// to Body which may contain structural context (history, sender labels).
|
||||
const commandSource = ctx.CommandBody ?? ctx.RawBody ?? ctx.Body ?? "";
|
||||
const triggerBodyNormalized = stripStructuralPrefixes(commandSource)
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
|
||||
// Use RawBody for reset trigger matching (clean message without structural context).
|
||||
const rawBody = ctx.RawBody ?? ctx.Body ?? "";
|
||||
// Use CommandBody/RawBody for reset trigger matching (clean message without structural context).
|
||||
const rawBody = commandSource;
|
||||
const trimmedBody = rawBody.trim();
|
||||
const resetAuthorized = resolveCommandAuthorization({
|
||||
ctx,
|
||||
@@ -290,7 +290,7 @@ export async function initSessionState(params: {
|
||||
...ctx,
|
||||
// Keep BodyStripped aligned with Body (best default for agent prompts).
|
||||
// RawBody is reserved for command/directive parsing and may omit context.
|
||||
BodyStripped: bodyStripped ?? ctx.Body ?? ctx.RawBody,
|
||||
BodyStripped: bodyStripped ?? ctx.Body ?? ctx.CommandBody ?? ctx.RawBody,
|
||||
SessionId: sessionId,
|
||||
IsNewSession: isNewSession ? "true" : "false",
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user