UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

62 lines (61 loc) • 1.96 kB
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(); }, }; }