@sema-agent/core
Version:
Stateless, task-oriented AI agent core
204 lines (203 loc) • 7.54 kB
JavaScript
import { stripMarkedReminderBlocks } from "../core/reminder-mint.js";
import { redactSecrets } from "../core/untrusted-egress.js";
import { isProtectedWrite } from "../core/tool-registry.js";
import { writeTargetPath } from "../tools/fs/safety.js";
export const STEP_CAP = 10;
const FIELD_MAX = 80;
const EDITED_FILES_CAP = 32;
function firstLine(s) {
const nl = s.indexOf("\n");
return nl === -1 ? s : s.slice(0, nl);
}
const REDACTION_MARKER_RE = /\[redacted(?:-[a-z]+)?\]/g;
export function cutAt(line, max) {
if (line.length <= max)
return line;
let at = max;
for (const m of line.matchAll(REDACTION_MARKER_RE)) {
const start = m.index;
const end = start + m[0].length;
if (start < at && at < end) {
at = start;
break;
}
if (start >= at)
break;
}
const lead = line.charCodeAt(at - 1);
if (at > 0 && lead >= 0xd800 && lead <= 0xdbff)
at -= 1;
return line.slice(0, at);
}
export function redactThenCut(s, max) {
return cutAt(redactSecrets(s), max);
}
function clip(s) {
return redactThenCut(firstLine(stripMarkedReminderBlocks(s)).trim(), FIELD_MAX);
}
export function extractTarget(args) {
if (args === null || typeof args !== "object")
return args === undefined ? "" : clip(String(args));
const a = args;
const pick = (k) => (typeof a[k] === "string" ? a[k] : undefined);
const primary = pick("file_path") ??
pick("path") ??
pick("notebook_path") ??
pick("command") ??
pick("pattern") ??
pick("url") ??
pick("query") ??
pick("prompt") ??
pick("description");
if (primary !== undefined)
return clip(primary);
try {
return clip(JSON.stringify(a, (_key, v) => (typeof v === "string" ? stripMarkedReminderBlocks(v) : v)));
}
catch {
return "";
}
}
function editTargetPath(toolName, args) {
const call = { toolName, args };
if (!isProtectedWrite(call) || args === null || typeof args !== "object")
return undefined;
const p = writeTargetPath(call);
return typeof p === "string" && p.length > 0 ? p : undefined;
}
function outputFirstLine(output) {
if (typeof output === "string")
return clip(output);
if (Array.isArray(output)) {
const t = output.find((b) => b && typeof b === "object" && typeof b.text === "string");
if (t)
return clip(t.text);
}
return "";
}
export class SubagentStepRecorder {
lockedSource;
lockedResolved = false;
parentToolCallId;
steps = [];
pending = new Map();
edits = new Map();
action;
actionTool;
actionTarget;
constructor(parentToolCallId) {
this.parentToolCallId = parentToolCallId;
}
lockTo(taskId) {
if (this.lockedResolved && this.lockedSource !== taskId)
this.resetRecorded();
this.lockedSource = taskId;
this.lockedResolved = true;
}
resetRecorded() {
this.steps.length = 0;
this.pending.clear();
this.edits.clear();
this.action = undefined;
this.actionTool = undefined;
this.actionTarget = undefined;
}
inScope(e) {
const { sourceTaskId: src, parentToolCallId: ptc } = e;
if (src === undefined)
return false;
if (this.lockedResolved)
return src === this.lockedSource;
if (this.parentToolCallId !== undefined && ptc !== this.parentToolCallId)
return false;
this.lockedSource = src;
this.lockedResolved = true;
return true;
}
record(e) {
try {
if (e.type === "tool_start") {
if (!this.inScope(e))
return;
const tool = e.label ?? e.toolName;
const target = extractTarget(e.args);
this.pending.set(e.toolCallId, { tool, target, ...(editTargetPath(e.toolName, e.args) ? { edit: editTargetPath(e.toolName, e.args) } : {}) });
this.action = target ? `${tool} ${target}`.slice(0, FIELD_MAX + tool.length + 1) : tool;
this.actionTool = tool;
this.actionTarget = target || undefined;
}
else if (e.type === "tool_end") {
if (!this.inScope(e))
return;
const start = this.pending.get(e.toolCallId);
this.pending.delete(e.toolCallId);
const tool = start?.tool ?? e.label ?? e.toolName;
const target = start?.target ?? "";
const body = outputFirstLine(e.output);
const outcome = e.isError ? cutAt(`error: ${body}`, FIELD_MAX) : body;
this.steps.push({ tool, target, outcome });
if (this.steps.length > STEP_CAP)
this.steps.shift();
const editPath = start?.edit;
if (editPath !== undefined && !e.isError) {
if (this.edits.has(editPath))
this.edits.set(editPath, this.edits.get(editPath) + 1);
else if (this.edits.size < EDITED_FILES_CAP)
this.edits.set(editPath, 1);
}
}
}
catch {
}
}
recentSteps() {
return this.steps.length > 0 ? this.steps.slice() : undefined;
}
editedFiles() {
return this.edits.size > 0 ? [...this.edits].map(([path, edits]) => ({ path, edits })) : undefined;
}
currentAction() {
return this.action;
}
currentActionStructured() {
return this.actionTool === undefined ? undefined : { toolName: this.actionTool, ...(this.actionTarget !== undefined ? { target: this.actionTarget } : {}) };
}
}
const stampOf = (m) => (typeof m.timestamp === "number" && Number.isFinite(m.timestamp) ? m.timestamp : undefined);
export function stepsFromMessages(messages, lastN) {
const results = new Map();
for (const m of messages) {
const msg = m;
if (msg.role === "toolResult" && typeof msg.toolCallId === "string") {
results.set(msg.toolCallId, { isError: msg.isError === true, body: outputFirstLineFromContent(msg.content), at: stampOf(msg) });
}
}
const steps = [];
for (const m of messages) {
const msg = m;
if (msg.role !== "assistant" || !Array.isArray(msg.content))
continue;
const calledAt = stampOf(msg);
for (const block of msg.content) {
const b = block;
if (b.type !== "toolCall" || typeof b.name !== "string")
continue;
const res = typeof b.id === "string" ? results.get(b.id) : undefined;
const outcome = res ? (res.isError ? cutAt(`error: ${res.body}`, FIELD_MAX) : res.body) : "";
const at = res ? res.at : calledAt;
steps.push({ tool: b.name, target: extractTarget(b.arguments), outcome, ...(at !== undefined ? { at } : {}), ...(res === undefined ? { inFlight: true } : {}) });
}
}
const cap = Math.max(0, Math.min(lastN, steps.length));
return steps.slice(steps.length - cap);
}
function outputFirstLineFromContent(content) {
if (typeof content === "string")
return clip(content);
if (Array.isArray(content)) {
const t = content.find((b) => b && typeof b === "object" && typeof b.text === "string");
if (t)
return clip(t.text);
}
return "";
}