UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

320 lines (319 loc) • 13.8 kB
import { createHash } from "node:crypto"; import { DEFAULT_CHARS_PER_TOKEN, estimateTokens } from "../internal/harness.js"; import { isToolResult } from "./message-utils.js"; import { physicalWindowOf } from "./context-settings.js"; import { PERSISTED_OUTPUT_PREFIX, offloadPagebackHint } from "./tool-result-store.js"; import { assertNoRetiredKeys } from "./retired-keys.js"; import { compactableToolNames } from "./tool-registry.js"; import { wireOf } from "./tool-name-readings.js"; const CC_CLEARED_MARKER = "[Old tool result content cleared]"; const CC_CLEARED_EXTENDED_PREFIX = "[Old tool result content cleared —"; const refNote = (ref) => offloadPagebackHint(ref, "cleared"); const mediaNote = (blocks) => { const byType = new Map(); for (const b of blocks) { const t = b.type ?? "untyped"; byType.set(t, (byType.get(t) ?? 0) + 1); } const n = blocks.length; if (byType.size === 1) { const [t, c] = [...byType][0]; return `${c} ${t} attachment${c === 1 ? "" : "s"} no longer visible after this clear`; } const breakdown = [...byType].map(([t, c]) => `${c} ${t}`).join(", "); return `${n} attachments (${breakdown}) no longer visible after this clear`; }; const clearedMarker = (notes) => { const present = notes.filter((n) => n !== undefined); return present.length > 0 ? `${CC_CLEARED_EXTENDED_PREFIX} ${present.join("; ")}]` : CC_CLEARED_MARKER; }; export const EDIT_FRACTION = 0.7; export const CONTEXT_OUTPUT_RESERVE_TOKENS = 20000; export const COMPACTION_TRIGGER_BUFFER_TOKENS = 13000; export function contextEditFrontier(window) { return Math.max(window - (CONTEXT_OUTPUT_RESERVE_TOKENS + COMPACTION_TRIGGER_BUFFER_TOKENS), Math.floor(window * EDIT_FRACTION)); } export const MICRO_COMPACT_MACHINES = Object.freeze(["off", "cc"]); export const CC_DEFAULT_KEEP_RECENT_TOOL_RESULTS = 5; export const MIN_CLEAR_SAVINGS_TOKENS = 20000; const MEDIA_BLOCK_SAVINGS_TOKENS = 2000; export const MIN_KEEP_RECENT_TOOL_RESULTS = 1; function resolveKeepRecentToolResults(value, defaultValue) { if (value === undefined) return defaultValue; if (!Number.isSafeInteger(value) || value < 0) { throw new TypeError(`keepRecentToolResults must be a non-negative safe integer (got ${String(value)}); ` + `omit it for the default ${defaultValue}`); } return Math.max(MIN_KEEP_RECENT_TOOL_RESULTS, value); } const COMPACTABLE_TOOLS = compactableToolNames(); const compactableResult = (compactable, recorded, toolNameOf) => compactable.has(wireOf("compaction.compactableResult", { toolName: toolNameOf !== undefined ? toolNameOf(recorded) : recorded })); function soleMarkerText(m) { const content = m.content; if (!Array.isArray(content) || content.length !== 1) return undefined; const text = content[0].text; return typeof text === "string" ? text : undefined; } function isCleared(m) { const text = soleMarkerText(m); if (text !== undefined && (text === CC_CLEARED_MARKER || text.startsWith(CC_CLEARED_EXTENDED_PREFIX))) return true; const content = m.content; if (!Array.isArray(content) || content.length === 0) return false; const first = content[0]; return first?.type === "text" && typeof first.text === "string" && first.text.startsWith(PERSISTED_OUTPUT_PREFIX); } export function dropEmptyFailureAssistants(messages) { const isJunk = (m) => { if (m.role !== "assistant") return false; const a = m; if (a.stopReason !== "aborted" && a.stopReason !== "error") return false; return a.content.every((c) => c.type === "text" ? (c.text ?? "").trim() === "" : c.type === "thinking" ? (c.thinking ?? "").trim() === "" : false); }; return messages.some(isJunk) ? messages.filter((m) => !isJunk(m)) : messages; } function structuralClearSavings(target) { const content = Array.isArray(target.content) ? target.content : []; let saved = 0; for (const c of content) { const block = c; if (block?.type === "text") { saved += typeof block.text === "string" ? Math.round(block.text.length / 4) : 0; } else { saved += MEDIA_BLOCK_SAVINGS_TOKENS; } } return saved; } function clearOneResult(target, offload) { const rawContent = Array.isArray(target.content) ? target.content : []; let ref; if (offload) { const fullText = rawContent .filter((c) => c?.type === "text" && typeof c.text === "string") .map((c) => c.text) .join("\n"); if (target.toolCallId && fullText.trim().length > 0) { try { ref = refNote(offload.persist(target.toolCallId, fullText)); } catch { ref = undefined; } } } const mediaBlocks = rawContent.filter((c) => c?.type !== "text"); const marker = clearedMarker([ref, mediaBlocks.length > 0 ? mediaNote(mediaBlocks) : undefined]); return { ...target, content: [{ type: "text", text: marker }] }; } function reportCleared(onCleared, clears, tokensSavedEstimate) { if (!onCleared || clears.length === 0) return; try { onCleared({ clears, tokensSavedEstimate }); } catch { } } const CONTEXT_EDIT_OPTION_KEYS = new Set([ "budgetTokens", "onCleared", "keepRecentToolResults", "offload", "anchoredTotalTokens", "charsPerToken", "compactableTools", "toolNameOf", "clearSource", ]); const RETIRED_CONTEXT_EDIT_OPTION_KEYS = { machine: 'the pass runs one machine (the CC form); the frontier on/off switch is `RunnerDeps.microCompact.machine: "off" | "cc"`', recognizeCcMarkers: "the cleared-marker forms are always recognized", }; export function clearStaleToolResults(messages, opts) { assertNoRetiredKeys(opts, RETIRED_CONTEXT_EDIT_OPTION_KEYS, "clearStaleToolResults option"); for (const key of Object.keys(opts)) { if (CONTEXT_EDIT_OPTION_KEYS.has(key) || Reflect.get(opts, key) === undefined) continue; const replacement = RETIRED_CONTEXT_EDIT_OPTION_KEYS[key]; throw new TypeError(`clearStaleToolResults: option "${key}" is not one this pass reads` + (replacement !== undefined ? ` — it is retired: ${replacement}` : "") + `. Legal keys: ${[...CONTEXT_EDIT_OPTION_KEYS].join(", ")}. Refused rather than ignored: a silently dropped option reads to the caller as in force.`); } const cpt = opts.charsPerToken ?? DEFAULT_CHARS_PER_TOKEN; let total; if (opts.anchoredTotalTokens !== undefined) { total = opts.anchoredTotalTokens; } else { total = 0; for (const m of messages) { total += estimateTokens(m, cpt); } } if (total <= opts.budgetTokens) { return messages; } const keep = resolveKeepRecentToolResults(opts.keepRecentToolResults, CC_DEFAULT_KEEP_RECENT_TOOL_RESULTS); const compactable = opts.compactableTools ?? COMPACTABLE_TOOLS; const candidates = messages.flatMap((m, i) => isToolResult(m) && compactableResult(compactable, m.toolName, opts.toolNameOf) ? [{ idx: i, target: m }] : []); const beyondKeep = candidates.slice(0, Math.max(0, candidates.length - keep)); const clearable = beyondKeep.filter(({ target }) => !isCleared(target)); if (clearable.length === 0) { return messages; } const tokensSaved = clearable.reduce((sum, { target }) => sum + structuralClearSavings(target), 0); if (tokensSaved < MIN_CLEAR_SAVINGS_TOKENS) { return messages; } const out = messages.slice(); const clears = []; for (const { idx, target } of clearable) { const src = opts.clearSource?.[idx]; const source = src !== undefined && isToolResult(src) && src.toolCallId === target.toolCallId ? src : target; out[idx] = clearOneResult(source, opts.offload); clears.push({ index: idx }); } reportCleared(opts.onCleared, clears, tokensSaved); return out; } export function resolveTriggerWindow(model, overlay) { const physical = physicalWindowOf(model, overlay); const declared = overlay?.autoCompactTokens ?? model.autoCompactTokens; if (declared !== undefined && Number.isFinite(declared) && declared > 0 && Number.isFinite(physical) && physical > 0 && declared > physical) { return { window: physical, clamped: true, declaredAutoCompactTokens: declared, physicalWindow: physical }; } return { window: declared ?? physical, clamped: false }; } export function editBudget(model, overlay) { const window = resolveTriggerWindow(model, overlay).window; if (!Number.isFinite(window) || window <= 0) { return Number.POSITIVE_INFINITY; } return contextEditFrontier(window); } export function fingerprintToolResultOccurrence(m) { const content = Array.isArray(m.content) ? (m.content) : []; const canonical = content.map((c) => { const block = c; if (block?.type === "text" && typeof block.text === "string") { return ["text", block.text]; } return [block?.type ?? "?", block?.mimeType ?? "", typeof block?.data === "string" ? block.data : ""]; }); return createHash("sha256").update(JSON.stringify(canonical)).digest("hex"); } export function createClearedProjectionLedger() { return { entries: new Map() }; } const OCCURRENCE_KEY_SEP = String.fromCharCode(0); function occurrenceGroupId(m) { return `${m.toolCallId}${OCCURRENCE_KEY_SEP}${m.timestamp ?? 0}`; } export function planRejectionClears(messages, opts) { const keep = resolveKeepRecentToolResults(opts.keepRecentToolResults, CC_DEFAULT_KEEP_RECENT_TOOL_RESULTS); const compactable = opts.compactableTools ?? COMPACTABLE_TOOLS; const candidates = messages.flatMap((m) => (isToolResult(m) && compactableResult(compactable, m.toolName, opts.toolNameOf) ? [m] : [])); const beyondKeep = candidates.slice(0, Math.max(0, candidates.length - keep)); const clearable = beyondKeep.flatMap((m) => { if (isCleared(m)) return []; const at = opts.keyOf(m); return at === undefined ? [] : [{ m, at }]; }); if (clearable.length === 0) return { declined: "no_candidates" }; const tokensSavedEstimate = clearable.reduce((sum, { m }) => sum + structuralClearSavings(m), 0); if (tokensSavedEstimate < MIN_CLEAR_SAVINGS_TOKENS) return { declined: "below_min_savings" }; const cleared = clearable.map(({ m, at }) => { const source = at.occurrence !== undefined && isToolResult(at.occurrence) ? at.occurrence : m; const clearedMsg = clearOneResult(source, opts.offload); return { key: at.key, groupCount: at.groupCount, marker: clearedMsg.content[0]?.text ?? CC_CLEARED_MARKER, fp: at.fp, toolCallId: m.toolCallId }; }); return { cleared, tokensSavedEstimate }; } export function replayClearedProjection(messages, ledger) { const groups = new Map(); const byIndex = new Map(); const byObject = new WeakMap(); messages.forEach((m, i) => { if (!isToolResult(m)) return; const g = occurrenceGroupId(m); const list = groups.get(g) ?? []; byIndex.set(i, { group: g, dupIdx: list.length }); byObject.set(m, { group: g, dupIdx: list.length }); list.push(i); groups.set(g, list); }); const mintKey = (hit) => { if (hit === undefined) return undefined; const list = groups.get(hit.group) ?? []; const occurrenceIdx = list[hit.dupIdx]; return { key: `${hit.group}${OCCURRENCE_KEY_SEP}${hit.dupIdx}`, groupCount: list.length, fp: occurrenceIdx === undefined ? "" : fingerprintToolResultOccurrence(messages[occurrenceIdx]), ...(occurrenceIdx === undefined ? {} : { occurrence: messages[occurrenceIdx] }), }; }; const index = { keyAt: (i) => mintKey(byIndex.get(i)), keyOf: (m) => { const direct = byObject.get(m); if (direct !== undefined) return mintKey(direct); if (!isToolResult(m)) return undefined; const g = occurrenceGroupId(m); const list = groups.get(g); return list !== undefined && list.length === 1 ? mintKey({ group: g, dupIdx: 0 }) : undefined; }, }; if (ledger.entries.size === 0) { return { messages, index }; } let out; const rekeyed = new Map(); for (const [key, entry] of ledger.entries) { const sep = key.lastIndexOf(OCCURRENCE_KEY_SEP); const group = key.slice(0, sep); const dupIdx = Number(key.slice(sep + 1)); const list = groups.get(group); if (list === undefined) continue; const m = list.length; const shrink = Math.max(0, entry.groupCount - m); const mapped = dupIdx - shrink; if (mapped < 0 || mapped >= m) continue; const targetIdx = list[mapped]; const target = messages[targetIdx]; if (soleMarkerText(target) !== entry.marker) { if (fingerprintToolResultOccurrence(target) !== entry.fp) continue; out = out ?? messages.slice(); out[targetIdx] = { ...target, content: [{ type: "text", text: entry.marker }] }; } rekeyed.set(`${group}${OCCURRENCE_KEY_SEP}${mapped}`, { marker: entry.marker, groupCount: m, fp: entry.fp }); } ledger.entries.clear(); for (const [k, v] of rekeyed) ledger.entries.set(k, v); return { messages: out ?? messages, index }; }