UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

53 lines (52 loc) • 1.75 kB
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(); }, }; }