@sema-agent/core
Version:
Stateless, task-oriented AI agent core
119 lines (118 loc) • 5 kB
JavaScript
import { userAttachmentOf } from "../../internal/llm.js";
import { isAbsoluteForFamily, pathFamilyOf } from "../../tools/fs/safety.js";
import { applyRecordedEdit } from "../../tools/fs/fs-shared.js";
import { normalizeFileText } from "../../tools/fs/encoding.js";
import { isOffloadedDetailReplacement } from "../tool-result-store.js";
const READ_TOOL = "Read";
const WRITE_TOOL = "Write";
const EDIT_TOOL = "Edit";
const RETRACTING_RESULTS = [
{ toolName: "NotebookEdit", type: "notebook-edit", pathField: "notebookPath" },
];
function replayedEditContent(rest) {
const originalFile = rest.originalFile;
if (typeof originalFile !== "string" || isOffloadedDetailReplacement(originalFile))
return undefined;
const member = (v) => (typeof v === "string" && !isOffloadedDetailReplacement(v) ? v : undefined);
let edits;
const rawEdits = rest.edits;
if (Array.isArray(rawEdits) && rawEdits.length > 0) {
const out = [];
for (const e of rawEdits) {
const r = e;
const oldS = member(r?.oldString);
const newS = member(r?.newString);
if (oldS === undefined || newS === undefined)
return undefined;
out.push({ oldString: oldS, newString: newS, replaceAll: r.replaceAll === true });
}
edits = out;
}
else {
const oldS = member(rest.oldString);
const newS = member(rest.newString);
if (oldS === undefined || newS === undefined)
return undefined;
edits = [{ oldString: oldS, newString: newS, replaceAll: rest.replaceAll === true }];
}
let working = originalFile;
for (const e of edits) {
if (e.oldString !== "" && !working.includes(e.oldString))
return undefined;
working = applyRecordedEdit(working, e);
}
if (normalizeFileText(working) !== working)
return undefined;
return working;
}
function cardOf(details) {
if (typeof details !== "object" || details === null)
return undefined;
const rest = details;
return typeof rest.type === "string" ? { type: rest.type, rest } : undefined;
}
function wholeFileFromReadCard(rest) {
const file = rest.file;
if (typeof file !== "object" || file === null)
return undefined;
const f = file;
if (typeof f.content !== "string" || f.truncatedByTokenCap === true)
return undefined;
if (f.startLine !== 1 || typeof f.numLines !== "number" || f.numLines !== f.totalLines)
return undefined;
return f.content;
}
export function wholeFileRecordsFromTranscript(messages) {
const byPath = new Map();
for (const m of messages) {
const attachment = userAttachmentOf(m);
if (attachment?.observation !== undefined) {
const { key, entry } = attachment.observation;
if (isAbsoluteForFamily(pathFamilyOf({ root: key }), key))
byPath.set(key, { path: key, observation: entry, at: m.timestamp });
continue;
}
if (m.role !== "toolResult" || m.isError)
continue;
const card = cardOf(m.details);
if (card === undefined)
continue;
const { type, rest } = card;
if (m.toolName === EDIT_TOOL && type === "edit") {
const changed = rest.filePath;
if (typeof changed !== "string")
continue;
const replayed = replayedEditContent(rest);
if (replayed !== undefined && isAbsoluteForFamily(pathFamilyOf({ root: changed }), changed)) {
byPath.set(changed, { path: changed, content: replayed, at: m.timestamp });
}
else {
byPath.delete(changed);
}
continue;
}
const retracts = RETRACTING_RESULTS.find((r) => r.toolName === m.toolName && r.type === type);
if (retracts !== undefined) {
const changed = rest[retracts.pathField];
if (typeof changed === "string")
byPath.delete(changed);
continue;
}
const isRead = m.toolName === READ_TOOL && type === "text";
const isWrite = m.toolName === WRITE_TOOL && (type === "create" || type === "update");
if (!isRead && !isWrite)
continue;
const nested = rest.file;
const holder = isRead ? (typeof nested === "object" && nested !== null ? nested : undefined) : rest;
const filePath = holder?.filePath;
if (typeof filePath !== "string" || !isAbsoluteForFamily(pathFamilyOf({ root: filePath }), filePath))
continue;
const content = isRead ? wholeFileFromReadCard(rest) : typeof rest.content === "string" ? rest.content : undefined;
if (content === undefined)
continue;
if (isOffloadedDetailReplacement(content))
continue;
byPath.set(filePath, { path: filePath, content, at: m.timestamp });
}
return [...byPath.values()];
}