termix / src /modules /ai /lib /compact.ts
lekmikdok's picture
Prepare Terax for web deployment
50fe3c9 verified
Raw
History Blame Contribute Delete
2.16 kB
import type { ModelMessage } from "ai";
const KEEP_TAIL = 8;
const ELISION_TEXT = "[elided to save context — see prior tool call in history]";
function approxBytes(messages: ModelMessage[]): number {
let n = 0;
for (const m of messages) {
if (typeof m.content === "string") n += m.content.length;
else if (Array.isArray(m.content)) {
for (const part of m.content as { type: string; [k: string]: unknown }[]) {
if (part.type === "text" && typeof part.text === "string") n += part.text.length;
else if (part.type === "tool-result") n += JSON.stringify(part.output ?? "").length;
else if (part.type === "tool-call") n += JSON.stringify(part.input ?? "").length;
else n += 64;
}
}
}
return n;
}
function elideToolResults(m: ModelMessage): ModelMessage {
if (m.role !== "tool" || !Array.isArray(m.content)) return m;
type ToolPart = { type: string; output?: unknown; [k: string]: unknown };
let touched = false;
const next = (m.content as ToolPart[]).map((part) => {
if (part.type !== "tool-result") return part;
if (
part.output &&
typeof part.output === "object" &&
(part.output as { __elided?: boolean }).__elided
) {
return part;
}
touched = true;
return {
...part,
output: { type: "text", value: ELISION_TEXT, __elided: true },
};
});
if (!touched) return m;
return { ...m, content: next } as ModelMessage;
}
/** Replace older tool-result outputs with a stub when the conversation
* approaches the model's context limit. Keeps the last KEEP_TAIL messages
* intact and never touches system messages. */
export function compactModelMessages(
messages: ModelMessage[],
contextLimit: number,
): ModelMessage[] {
const approxTokens = approxBytes(messages) / 4;
if (approxTokens < 0.7 * contextLimit) return messages;
const out = messages.slice();
const stopIdx = Math.max(0, out.length - KEEP_TAIL);
for (let i = 0; i < stopIdx; i++) {
if (out[i].role === "system") continue;
out[i] = elideToolResults(out[i]);
if (approxBytes(out) / 4 < 0.6 * contextLimit) break;
}
return out;
}