@sema-agent/core
Version:
Stateless, task-oriented AI agent core
53 lines (52 loc) • 1.75 kB
JavaScript
import { occurrenceClock } from "./tool-occurrence.js";
export const TOOL_HEARTBEAT_MS = 30_000;
export const TOOL_PROGRESS_MIN_INTERVAL_MS = 1_000;
export function createToolProgressLane(push, ident, occurrences) {
let timer;
const arm = () => {
if (timer !== undefined)
return;
timer = setInterval(beat, TOOL_HEARTBEAT_MS);
timer.unref?.();
};
const disarm = () => {
if (timer === undefined)
return;
clearInterval(timer);
timer = undefined;
};
occurrences.onOpen(arm);
const emit = (body) => {
const now = occurrenceClock();
const unseen = occurrences.get(body.toolCallId) === undefined;
const occ = occurrences.ensure({ toolCallId: body.toolCallId, spelledName: body.toolName, args: undefined });
if (!unseen && now - occ.lastHeardAt < TOOL_PROGRESS_MIN_INTERVAL_MS)
return;
occurrences.heard(occ.toolCallId);
try {
push({ type: "tool_progress", ...body, elapsedTimeSeconds: Math.floor((now - occ.startedAt) / 1000), ...ident() });
}
catch {
}
};
function beat() {
const now = occurrenceClock();
const inFlight = occurrences.live().filter((o) => o.beating);
if (inFlight.length === 0)
return disarm();
if (inFlight.every((o) => o.awaitingUser > 0))
return;
for (const o of inFlight) {
if (now - o.lastHeardAt < TOOL_HEARTBEAT_MS)
continue;
emit({ toolCallId: o.toolCallId, toolName: o.spelledName });
}
}
return {
emit,
clear() {
occurrences.quiesce();
disarm();
},
};
}