@sema-agent/core
Version:
Stateless, task-oriented AI agent core
104 lines (103 loc) • 4.86 kB
JavaScript
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(),
};
}