@sema-agent/core
Version:
Stateless, task-oriented AI agent core
62 lines (61 loc) • 1.96 kB
JavaScript
export const TOOL_HEARTBEAT_MS = 30_000;
export const TOOL_PROGRESS_MIN_INTERVAL_MS = 1_000;
const monotonicNow = () => (typeof performance !== "undefined" ? performance.now() : Date.now());
export function createToolProgressLane(push, ident) {
const inFlight = new Map();
let timer;
const arm = () => {
if (timer !== undefined)
return;
timer = setInterval(beat, TOOL_HEARTBEAT_MS);
timer.unref?.();
};
const emit = (body) => {
const now = monotonicNow();
let live = inFlight.get(body.toolCallId);
if (live === undefined) {
live = { toolName: body.toolName, startedAt: now, lastAt: now - TOOL_PROGRESS_MIN_INTERVAL_MS };
inFlight.set(body.toolCallId, live);
arm();
}
if (now - live.lastAt < TOOL_PROGRESS_MIN_INTERVAL_MS)
return;
live.lastAt = now;
try {
push({ type: "tool_progress", ...body, elapsedTimeSeconds: Math.floor((now - live.startedAt) / 1000), ...ident() });
}
catch {
}
};
function beat() {
const now = monotonicNow();
for (const [toolCallId, live] of inFlight) {
if (now - live.lastAt < TOOL_HEARTBEAT_MS)
continue;
emit({ toolCallId, toolName: live.toolName });
}
}
const disarm = () => {
if (timer === undefined)
return;
clearInterval(timer);
timer = undefined;
};
return {
emit,
noteStart(toolCallId, toolName) {
const startedAt = monotonicNow();
inFlight.set(toolCallId, { toolName, startedAt, lastAt: startedAt });
arm();
},
noteEnd(toolCallId) {
inFlight.delete(toolCallId);
if (inFlight.size === 0)
disarm();
},
clear() {
inFlight.clear();
disarm();
},
};
}