UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

104 lines (103 loc) • 4.86 kB
import {} from "../../internal/harness.js"; import { readMember } from "../thrown-value.js"; import { refusedBeforeRelease } from "./tool-not-executed.js"; import { MCP_DELIVERY_VERDICTS } from "../mcp-failure.js"; import { closeNeverExecuted } from "../session-reconcile.js"; import { TOOL_SEARCH_NAME } from "./tool-disclosure.js"; import { toolOutputFrom } from "./tool-output-projection.js"; import { TASK_CREATE_TOOL_NAME, TASK_UPDATE_TOOL_NAME, TODO_WRITE_TOOL_NAME } from "../tool-catalog-entries.js"; const BATCH_RESPONSE_MAX_CHARS = 500; export function batchResponseDigest(result) { const content = result !== null && typeof result === "object" ? result.content : result; if (content === undefined || content === null) return undefined; let text; if (typeof content === "string") { text = content; } else if (Array.isArray(content)) { text = content .map((c) => (c !== null && typeof c === "object" && typeof c.text === "string" ? c.text : "")) .filter((t) => t.length > 0) .join("\n"); } else { try { text = JSON.stringify(content) ?? ""; } catch { return undefined; } } if (text.length === 0) return undefined; return text.length > BATCH_RESPONSE_MAX_CHARS ? `${text.slice(0, BATCH_RESPONSE_MAX_CHARS)}…[+${text.length - BATCH_RESPONSE_MAX_CHARS} chars truncated]` : text; } export function toolEndBodyFrom(result, isError, leg, gate, gatedCallIdOfRun) { const o = toolOutputFrom(result, leg.mediaByteCapBytes); const st = leg.structured(result); const det = isError ? readMember(result, "details") : undefined; const codeRaw = readMember(det, "code"); const kindRaw = readMember(det, "errorKind"); const code = typeof codeRaw === "string" ? codeRaw : typeof kindRaw === "string" ? kindRaw : undefined; const deliveredRaw = readMember(det, "delivered"); const delivered = typeof deliveredRaw === "string" && MCP_DELIVERY_VERDICTS.includes(deliveredRaw) ? deliveredRaw : undefined; const gatedCallId = code === "gate.parked" ? gatedCallIdOfRun : undefined; return { ...(o !== undefined ? { output: o.output, ...(o.truncated ? { truncated: true } : {}), ...(o.totalChars !== undefined ? { totalChars: o.totalChars } : {}) } : {}), ...(st !== undefined ? { structured: st } : {}), ...(typeof code === "string" ? { errorCode: code } : {}), ...(delivered !== undefined ? { delivered } : {}), ...(gatedCallId !== undefined ? { gatedCallId } : {}), ...(gate !== undefined ? { gate } : {}), }; } const startFrameOf = (o, recovered) => ({ type: "tool_start", toolCallId: o.toolCallId, toolName: o.spelledName, ...(o.label !== undefined ? { label: o.label } : {}), args: o.args, ...(recovered ? { recovered: true } : {}) }); export function announceToolCall(table, start, frames) { table.open(start); frames.push(startFrameOf(start, false)); table.noteStartEmitted(start.toolCallId); } export function emitSettled(table, start, end, frames) { const occ = table.ensure(start); if (!occ.startEmitted) frames.push(startFrameOf(occ, true)); const gate = occ.gateRounds.at(-1); const isError = end.isError || gate?.disposition.kind === "denied", notExecuted = end.notExecuted === true || refusedBeforeRelease(occ); table.close(occ.toolCallId, end.interrupted === true ? "interrupted" : "settled"); frames.push({ type: "tool_end", toolCallId: occ.toolCallId, toolName: occ.spelledName, ...(occ.label !== undefined ? { label: occ.label } : {}), isError, ...toolEndBodyFrom(end.result, isError, frames, gate, end.gatedCallIdOfRun), ...(gate?.disposition.kind === "denied" && occ.wireName !== undefined ? { wireName: occ.wireName } : {}), ...(notExecuted ? { notExecuted: true } : {}), }); return { ...(gate !== undefined ? { gate } : {}), isError, ...(notExecuted ? { notExecuted: true } : {}) }; } export function reconciledEnd(orphan) { return { result: { content: orphan.text, details: { code: orphan.errorKind } }, isError: true, interrupted: true, ...(closeNeverExecuted(orphan.errorKind) ? { notExecuted: true } : {}) }; } export function writeFamilyOfCanonical(name) { if (name === TASK_CREATE_TOOL_NAME || name === TASK_UPDATE_TOOL_NAME) return "task"; if (name === TODO_WRITE_TOOL_NAME) return "todo"; if (name === TOOL_SEARCH_NAME) return "tool_search"; return undefined; } export function toolResultMsg(toolCallId, toolName, text, isError) { return { role: "toolResult", toolCallId, toolName, content: [{ type: "text", text }], isError, timestamp: Date.now(), }; }