UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

163 lines (162 loc) • 6.53 kB
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)); }