@sema-agent/core
Version:
Stateless, task-oriented AI agent core
124 lines (123 loc) • 4.58 kB
JavaScript
import { mintGateOutcome } from "./gate-exit.js";
export const occurrenceClock = () => (typeof performance !== "undefined" ? performance.now() : Date.now());
const TERMINAL = { announced: false, released: false, settled: true, interrupted: true };
export function createToolOccurrenceTable() {
const byId = new Map();
const attempts = new Map();
let listener;
const held = (toolCallId, verb) => {
const occ = byId.get(toolCallId);
if (occ === undefined)
throw new Error(`tool occurrence: ${verb} for call "${toolCallId}", which this leg does not hold`);
return occ;
};
const open = (start) => {
const attempt = (attempts.get(start.toolCallId) ?? 0) + 1;
attempts.set(start.toolCallId, attempt);
const now = occurrenceClock();
const occ = {
toolCallId: start.toolCallId,
spelledName: start.spelledName,
...(start.wireName !== undefined ? { wireName: start.wireName } : {}),
...(start.label !== undefined ? { label: start.label } : {}),
args: start.args,
attempt,
life: "announced",
awaitingUser: 0,
gateRounds: [],
startEmitted: false,
startedAt: now,
lastHeardAt: now,
beating: true,
};
byId.delete(start.toolCallId);
byId.set(start.toolCallId, occ);
listener?.();
return occ;
};
const awaitUser = (toolCallId) => {
const occ = byId.get(toolCallId);
if (occ === undefined)
return () => { };
occ.awaitingUser += 1;
let returned = false;
return () => {
if (returned)
return;
returned = true;
if (byId.get(toolCallId) !== occ)
return;
occ.awaitingUser -= 1;
if (occ.awaitingUser === 0)
occ.lastHeardAt = occurrenceClock();
};
};
return {
awaitUser,
whileAwaitingUser(toolCallId, ask) {
const resumeFromUser = awaitUser(toolCallId);
let answer;
try {
answer = ask();
}
catch (err) {
resumeFromUser();
throw err;
}
if (answer instanceof Promise)
answer.then(resumeFromUser, resumeFromUser);
else
resumeFromUser();
return answer;
},
get: (toolCallId) => byId.get(toolCallId),
live: () => [...byId.values()],
open,
ensure: (start) => byId.get(start.toolCallId) ?? open(start),
noteStartEmitted(toolCallId) {
held(toolCallId, "a start frame").startEmitted = true;
},
release(toolCallId) {
const occ = held(toolCallId, "release");
if (occ.life !== "announced")
throw new Error(`tool occurrence: call "${toolCallId}" (attempt ${occ.attempt}) cannot be released from "${occ.life}"`);
occ.life = "released";
},
gateRound(toolCallId, outcome, finalInput) {
const occ = held(toolCallId, "a gate round");
occ.gateRounds.push(outcome);
if (finalInput !== undefined)
occ.finalInput = finalInput;
},
refuse(toolCallId, layer) {
const occ = held(toolCallId, "a refusal");
const latest = occ.gateRounds.at(-1);
const round = mintGateOutcome({ deniedBy: layer, ...(latest?.settlement !== undefined && latest.origin !== undefined ? { settled: { settlement: latest.settlement, origin: latest.origin } } : {}) });
occ.gateRounds.push(round);
return round;
},
heard(toolCallId) {
const occ = byId.get(toolCallId);
if (occ !== undefined)
occ.lastHeardAt = occurrenceClock();
},
close(toolCallId, life) {
const occ = byId.get(toolCallId);
if (occ === undefined)
return undefined;
if (TERMINAL[occ.life])
throw new Error(`tool occurrence: call "${toolCallId}" (attempt ${occ.attempt}) is already ${occ.life}`);
occ.life = life;
occ.beating = false;
byId.delete(toolCallId);
return occ;
},
quiesce() {
for (const occ of byId.values())
occ.beating = false;
},
onOpen(l) {
listener = l;
},
};
}