@sema-agent/core
Version:
Stateless, task-oriented AI agent core
163 lines (162 loc) • 6.53 kB
JavaScript
import { DEFAULT_CHARS_PER_TOKEN, estimateContextTokens, estimateTokens } from "../internal/harness.js";
import { isToolResult, messageRole } from "./message-utils.js";
import { physicalWindowOf } from "./context-settings.js";
const GUARD_FRACTION = 0.85;
const GUARD_HEADROOM_TOKENS = 23000;
function isSummary(m) {
return messageRole(m) === "compactionSummary";
}
export function trimToBudget(messages, budgetTokens, anchoredTotalTokens, charsPerToken) {
const cpt = charsPerToken ?? DEFAULT_CHARS_PER_TOKEN;
const anchored = anchoredTotalTokens !== undefined;
let structuralTotal = 0;
for (const m of messages) {
structuralTotal += estimateTokens(m, cpt);
}
const total = anchored ? anchoredTotalTokens : structuralTotal;
if (total <= budgetTokens || messages.length <= 1) {
return messages;
}
const anchorIdx = anchored ? (estimateContextTokens(messages, cpt).lastUsageIndex ?? -1) : -1;
const summaries = messages.filter(isSummary);
let summaryTokens = 0;
for (const s of summaries) {
summaryTokens += estimateTokens(s, cpt);
}
const nonSummary = [];
const reductions = [];
let reductionSum = 0;
for (let i = 0; i < messages.length; i++) {
if (isSummary(messages[i]))
continue;
nonSummary.push(messages[i]);
const r = i > anchorIdx ? estimateTokens(messages[i], cpt) : 0;
reductions.push(r);
reductionSum += r;
}
const requiredReduction = total - budgetTokens;
let start;
if (anchored && reductionSum <= requiredReduction) {
start = nonSummary.length - 1;
}
else {
const tailBudget = Math.max(0, reductionSum - requiredReduction);
let acc = 0;
start = nonSummary.length;
for (let i = nonSummary.length - 1; i >= 0; i--) {
acc += reductions[i];
if (acc > tailBudget && i + 1 < nonSummary.length) {
start = i + 1;
break;
}
start = i;
}
}
while (start < nonSummary.length && isToolResult(nonSummary[start])) {
start++;
}
if (start >= nonSummary.length) {
start = nonSummary.length - 1;
while (start > 0 && isToolResult(nonSummary[start])) {
start--;
}
if (isToolResult(nonSummary[start])) {
start = nonSummary.length - 1;
}
}
const keptTail = new Set(nonSummary.slice(start));
return messages.filter((m) => isSummary(m) || keptTail.has(m));
}
export const CONTEXT_TRIM_NOTICE = "[Note: earlier messages were omitted from this request to fit the context window — the full " +
"history is preserved in the session. If an earlier instruction, constraint, or detail seems to " +
"be missing, it may be in the omitted portion; say so instead of guessing.]";
export function insertTrimNotice(trimmed) {
const notice = { role: "user", content: CONTEXT_TRIM_NOTICE, timestamp: 0 };
let at = 0;
while (at < trimmed.length && isSummary(trimmed[at]))
at++;
return [...trimmed.slice(0, at), notice, ...trimmed.slice(at)];
}
export function engineRegionCovers(m, start, len) {
const meta = m;
if (meta.engineMinted === true)
return true;
if (Array.isArray(meta.engineSegments))
return meta.engineSegments.some((seg) => seg.start <= start && start + len <= seg.end);
if (typeof meta.enginePrefixChars === "number")
return start + len <= meta.enginePrefixChars;
return false;
}
function singleText(m) {
const c = m.content;
if (typeof c === "string")
return c;
if (Array.isArray(c) && c.length === 1) {
const b = c[0];
if (b?.type === "text" && typeof b.text === "string")
return b.text;
}
return undefined;
}
export function protectGitFrame(before, trimmed, budgetTokens, frame, charsPerToken) {
const cpt = charsPerToken ?? DEFAULT_CHARS_PER_TOKEN;
let carrier;
for (let i = before.length - 1; i >= 0; i--) {
const candidate = before[i];
if (candidate === undefined || messageRole(candidate) !== "user")
continue;
const text = singleText(candidate);
if (text === undefined)
continue;
const at = text.indexOf(frame.protectedText);
if (at === -1 || !engineRegionCovers(candidate, at, frame.protectedText.length))
continue;
carrier = candidate;
break;
}
if (carrier === undefined)
return { messages: trimmed, action: "absent" };
let out = trimmed;
let action = "kept";
if (!out.includes(carrier)) {
carrier = { role: "user", content: [{ type: "text", text: frame.protectedText }], timestamp: carrier.timestamp ?? Date.now(), engineMinted: true };
let at = 0;
for (; at < out.length; at++) {
const head = out[at];
if (head === undefined || (!isSummary(head) && singleText(head) !== CONTEXT_TRIM_NOTICE))
break;
}
out = [...out.slice(0, at), carrier, ...out.slice(at)];
action = "reinserted";
}
const segTokens = (text) => estimateTokens({ role: "user", content: [{ type: "text", text }] }, cpt);
const coreTokens = (view, frameText) => {
let t = segTokens(frameText);
for (const m of view)
if (isSummary(m))
t += estimateTokens(m, cpt);
return t;
};
if (coreTokens(out, frame.protectedText) <= budgetTokens)
return { messages: out, action };
if (frame.substitute !== undefined) {
const text = singleText(carrier);
const shrunkText = text.replace(frame.substitute.find, () => frame.substitute.replace);
if (shrunkText !== text) {
const shrunkCarrier = { ...carrier, content: [{ type: "text", text: shrunkText }] };
const idx = out.indexOf(carrier);
out = [...out.slice(0, idx), shrunkCarrier, ...out.slice(idx + 1)];
if (coreTokens(out, frame.substitute.replace) <= budgetTokens)
return { messages: out, action: "shrunk" };
return { action: "over_budget" };
}
}
return { action: "over_budget" };
}
export function guardBudget(model, overlay) {
const window = physicalWindowOf(model, overlay);
if (!Number.isFinite(window) || window <= 0) {
return Number.POSITIVE_INFINITY;
}
return Math.max(window - GUARD_HEADROOM_TOKENS, Math.floor(window * GUARD_FRACTION));
}