UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

190 lines (189 loc) • 7.18 kB
import { stripMarkedReminderBlocks } from "../core/reminder-mint.js"; import { renderContentBlock } from "../internal/llm.js"; import { egressText } 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(egressText("same-principal", 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); return Array.isArray(output) ? clip(output.map(renderContentBlock).join("\n")) : ""; } 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: outputFirstLine(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); }