UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

127 lines (126 loc) • 5.72 kB
import { emitTrace } from "../trace.js"; import { buildToolResultRef, OFFLOAD_TOOL_NAME, PERSISTED_OUTPUT_PREFIX, toolResultContentSegment, toolResultProvenanceOf, } from "../tool-result-store.js"; import { compactionEnabled, contextOverlayFor } from "../context-settings.js"; import { DEFAULT_COMPACTION_SETTINGS } from "../../internal/harness.js"; export function buildWorkingFileAttachments(spec, prepared) { if (spec.compaction?.attachWorkingFiles === false || !prepared.readTaskFile) return undefined; return { readFile: prepared.readTaskFile, ...(prepared.recentlyReadFiles ? { recentlyReadFiles: prepared.recentlyReadFiles } : {}), ...(prepared.normalizeAttachmentPath ? { normalizePath: prepared.normalizeAttachmentPath } : {}), ...(prepared.isDedupStubResult ? { isDedupStubResult: prepared.isDedupStubResult } : {}), ...(prepared.instructionSources ? { instructionSourcePaths: prepared.instructionSources.filter((s) => s.contentHash !== null).map((s) => s.path) } : {}), ...(typeof spec.compaction?.attachWorkingFiles === "object" ? spec.compaction.attachWorkingFiles : undefined), }; } export function contextInstructionFilesOption(prepared) { return prepared.projectInstructionContent !== undefined ? { contextInstructionFiles: prepared.projectInstructionContent } : {}; } export function forkContextOption(prepared, disable) { return disable ? {} : { forkContext: prepared.lastBrainContext }; } export function centerAdoptionOption(prepared) { const ca = prepared.centerCompactionCandidate?.(); return ca !== undefined ? { centerAdoption: ca } : {}; } export const STALE_OFFLOAD_DEFAULT_KEEP_RECENT_PER_TOOL = 3; export const STALE_OFFLOAD_DEFAULT_MIN_SAVINGS_CHARS = 2000; export function resolveStaleToolResultOffload(knob) { if (knob === undefined) return undefined; const check = (name, v, fallback) => { if (v === undefined) return fallback; if (!Number.isInteger(v) || v < 0) { const e = new Error(`compaction.staleToolResultOffload.${name} must be a non-negative integer, got ${String(v)}`); e.code = "config.stale_tool_result_offload_invalid"; throw e; } return v; }; return { keepRecentPerTool: check("keepRecentPerTool", knob.keepRecentPerTool, STALE_OFFLOAD_DEFAULT_KEEP_RECENT_PER_TOOL), minSavingsChars: check("minSavingsChars", knob.minSavingsChars, STALE_OFFLOAD_DEFAULT_MIN_SAVINGS_CHARS), }; } export function buildStaleOffloadPointer(toolName, ref, chars) { return (`[Stale tool result offloaded to save context: ${chars} chars from an earlier "${toolName}" call ` + `saved to persisted output ref "${ref}". Newer results of this tool are still shown in full below; ` + `use the ${OFFLOAD_TOOL_NAME} tool with this ref if you need the offloaded content again.]`); } function toolResultText(m) { return m.content .filter((b) => b.type === "text") .map((b) => b.text) .join("\n"); } export async function projectStaleToolResults(context, cfg, store, sessionId, writtenRefs) { const byTool = new Map(); context.messages.forEach((m, i) => { if (m.role !== "toolResult" || m.isError) return; const list = byTool.get(m.toolName); const row = { idx: i, msg: m }; if (list === undefined) byTool.set(m.toolName, [row]); else list.push(row); }); const replacements = new Map(); for (const [toolName, rows] of byTool) { const staleCount = rows.length - cfg.keepRecentPerTool; for (let k = 0; k < staleCount; k++) { const { idx, msg } = rows[k]; const text = toolResultText(msg); if (text.startsWith(PERSISTED_OUTPUT_PREFIX)) continue; const ref = buildToolResultRef(sessionId, msg.toolCallId, toolResultContentSegment(text)); const pointer = buildStaleOffloadPointer(toolName, ref, text.length); if (text.length - pointer.length < cfg.minSavingsChars) continue; if (!writtenRefs.has(ref)) { try { await store.put(ref, text, toolResultProvenanceOf(sessionId)); writtenRefs.add(ref); } catch { continue; } } replacements.set(idx, { msg, pointer }); } } if (replacements.size === 0) return context; return { ...context, messages: context.messages.map((m, i) => { const hit = replacements.get(i); if (hit === undefined) return m; const rest = hit.msg.content.filter((b) => b.type !== "text"); return { ...hit.msg, content: [{ type: "text", text: hit.pointer }, ...rest] }; }), }; } export function emitInputTruncated(tracer, taskId) { return (info) => emitTrace(tracer, () => ({ kind: "compaction.input_truncated", version: 1, taskId, label: info.label, droppedChars: info.droppedChars, keptChars: info.keptChars, ts: Date.now(), })); } export function legContextOverlayOption(prepared, runner) { const overlay = contextOverlayFor(runner.deps.contextSettings, prepared.harness.getModelRef(), runner.deps); return overlay !== undefined ? { contextOverlay: overlay } : {}; } export function legCompactionSettings(spec, overlay) { return { ...DEFAULT_COMPACTION_SETTINGS, ...spec.compaction, enabled: compactionEnabled(spec.compaction?.enabled, overlay) }; }