UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

217 lines (216 loc) • 9.01 kB
export const MAX_TIMER_DELAY_MS = 2_147_483_647; export function resolveStallTimeoutMs(value, knob) { if (value === undefined) return undefined; if (!Number.isFinite(value) || value < 0) { const e = new Error(`${knob} must be a non-negative finite number of milliseconds (got ${String(value)})`); e.code = "config.stall_timeout_invalid"; throw e; } if (value > MAX_TIMER_DELAY_MS) { const e = new Error(`${knob} must not exceed ${MAX_TIMER_DELAY_MS}ms (~24.8 days) — a host timer truncates a larger delay and fires immediately, so this watchdog would abort every call instead of tolerating a long one (got ${String(value)}). Pass 0 to disable it.`); e.code = "config.stall_timeout_invalid"; throw e; } return value; } export const STALL_FIRST_BYTE_MS = 300_000; export const STALL_FIRST_TOKEN_MS = 600_000; export const STALL_IDLE_MS = 300_000; export const RETIRED_TIMEOUT_KNOBS = { construction: { connectTimeoutMs: "firstByteTimeoutMs" }, advisory: { connectMs: "firstByteMs" }, }; export function refuseRetiredTimeoutKnobs(holder, level, where) { if (holder === undefined || holder === null) return; for (const [retired, replacement] of Object.entries(RETIRED_TIMEOUT_KNOBS[level])) { if (!(retired in holder)) continue; const e = new Error(`${where}: \`${retired}\` was removed in 7.33.0 and is refused rather than ignored — the fetch()→response-headers wait it bounded is \`${replacement}\` (0 = off); the TCP + TLS connect is the transport's own clock, tuned on the process dispatcher (setGlobalDispatcher(new Agent({ connect: { timeout } })))`); e.code = "config.stall_timeout_retired"; throw e; } } export const FIRST_BYTE_UPLOAD_BYTES_PER_SECOND = 32_768; export const FIRST_BYTE_RETRY_BUDGET = 1; export function resolveFirstByteRetries(value) { if (value === undefined) return FIRST_BYTE_RETRY_BUDGET; if (value === 0 || value === 1) return value; const shown = typeof value === "string" ? JSON.stringify(value) : typeof value === "bigint" ? `${value}n` : typeof value === "object" && value !== null ? "an object" : String(value); const e = new Error(`firstByteRetries must be 0 or 1 (got ${shown}) — 1 re-sends a first-byte stall once at a widened window (the default), 0 ends the call at the first window so the caller's own failover takes it`); e.code = "config.stall_timeout_invalid"; throw e; } export const FIRST_BYTE_RETRY_WINDOW_FACTOR = 2; export function resolveWatchdogMs(configured, advisory, engineDefault, knob) { return resolveStallTimeoutMs(configured ?? advisory, knob) ?? engineDefault; } export function firstByteWindow(knobMs, bodyBytes, retryIndex) { if (knobMs === undefined || knobMs <= 0) return { knobMs: 0, allowanceMs: 0, windowMs: 0 }; const widened = knobMs * FIRST_BYTE_RETRY_WINDOW_FACTOR ** Math.max(0, retryIndex); const allowanceMs = Math.ceil(Math.max(0, bodyBytes) / FIRST_BYTE_UPLOAD_BYTES_PER_SECOND) * 1000; return { knobMs: widened, allowanceMs, windowMs: Math.min(widened + allowanceMs, MAX_TIMER_DELAY_MS) }; } export function firstByteLaneKnobMs(knobMs) { let total = 0; for (let retryIndex = 0; retryIndex <= FIRST_BYTE_RETRY_BUDGET; retryIndex++) total += firstByteWindow(knobMs, 0, retryIndex).knobMs; return total; } export function createAttemptController(firstByteWindowMs, outerSignal) { const ac = new AbortController(); let timedOut = false; const onAbort = () => ac.abort(outerSignal?.reason); if (outerSignal) { if (outerSignal.aborted) ac.abort(outerSignal.reason); else outerSignal.addEventListener("abort", onAbort, { once: true }); } let timer; if (firstByteWindowMs && firstByteWindowMs > 0) { timer = setTimeout(() => { timedOut = true; ac.abort(); }, firstByteWindowMs); } const clearTimer = () => { if (timer) clearTimeout(timer); timer = undefined; }; return { signal: ac.signal, timedOut: () => timedOut, clearTimer, abort: () => ac.abort(), dispose: () => { clearTimer(); if (outerSignal) outerSignal.removeEventListener("abort", onAbort); }, }; } export const BRAIN_CALL_GUARDRAIL_DEFAULT_MS = 3_600_000; export const BRAIN_CALL_GUARDRAIL_ERROR_CODE = "brain.call_guardrail_timeout"; export class BrainCallGuardrailTimeoutError extends Error { code = BRAIN_CALL_GUARDRAIL_ERROR_CODE; limitMs; constructor(limitMs) { super(`the engine stopped waiting for a brain call that ran past its ${limitMs}ms guardrail — the call was abandoned (a Brain that ignores options.signal may still be running)`); this.name = "BrainCallGuardrailTimeoutError"; this.limitMs = limitMs; } } export function resolveBrainCallGuardrailMs(knob) { if (knob === undefined) return BRAIN_CALL_GUARDRAIL_DEFAULT_MS; if (knob === false || knob === 0) return undefined; if (typeof knob !== "number" || !Number.isFinite(knob) || knob < 0) { const e = new Error(`brainCallGuardrailMs must be a positive number of milliseconds, 0, or false (got ${String(knob)})`); e.code = "config.brain_call_guardrail_invalid"; throw e; } if (knob > MAX_TIMER_DELAY_MS) { const e = new Error(`brainCallGuardrailMs must not exceed ${MAX_TIMER_DELAY_MS}ms (~24.8 days) — a host timer truncates a larger delay and fires immediately, so the backstop would abort every model call (got ${String(knob)}). Pass false or 0 to turn the backstop off.`); e.code = "config.brain_call_guardrail_invalid"; throw e; } return knob; } function armCallGuardrail(limitMs, outerSignal, ref) { const ac = new AbortController(); if (outerSignal !== undefined) { if (outerSignal.aborted) ac.abort(outerSignal.reason); } const forwardAbort = () => { ac.abort(outerSignal === undefined ? undefined : outerSignal.reason); }; if (outerSignal !== undefined && !outerSignal.aborted) { outerSignal.addEventListener("abort", forwardAbort, { once: true }); } let expired; let rejectExpiry = () => undefined; const expiry = new Promise((_resolve, reject) => { rejectExpiry = reject; }); expiry.catch(() => undefined); const timer = setTimeout(() => { const err = new BrainCallGuardrailTimeoutError(limitMs); expired = err; ref.timedOut ??= err; ac.abort(err); rejectExpiry(err); }, limitMs); return { signal: ac.signal, race: async (p) => { if (expired !== undefined) throw expired; const value = await Promise.race([p, expiry]); if (expired !== undefined) throw expired; return value; }, disarm: () => { clearTimeout(timer); if (outerSignal !== undefined) outerSignal.removeEventListener("abort", forwardAbort); }, }; } export function withBrainCallGuardrail(streamFn, limitMs, ref) { return async (model, context, options) => { const gate = armCallGuardrail(limitMs, options?.signal, ref); let inner; try { inner = await gate.race(Promise.resolve(streamFn(model, context, { ...options, signal: gate.signal }))); } catch (err) { gate.disarm(); throw err; } return { [Symbol.asyncIterator]() { const it = inner[Symbol.asyncIterator](); return { next: async () => { let step; try { step = await gate.race(Promise.resolve(it.next())); } catch (err) { gate.disarm(); throw err; } if (step.done === true) gate.disarm(); return step; }, return: async () => { gate.disarm(); const release = it.return; if (release !== undefined) { Promise.resolve(release.call(it)).catch(() => undefined); } return { value: undefined, done: true }; }, }; }, result: async () => { try { return await gate.race(Promise.resolve(inner.result())); } finally { gate.disarm(); } }, }; }; }