UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

119 lines (118 loc) • 6.34 kB
import {} from "../../internal/harness.js"; import { openSystemReminder } from "../reminder-mint.js"; import { emitTrace } from "../trace.js"; export const LIMIT_APPROACH_DEFAULT_THRESHOLDS = [0.8, 0.95]; export const DEFAULT_PRECALL_OUTPUT_TOKENS = 4096; export function limitApproachFrames(index, axis, percent, mark) { return index === 0 ? `${openSystemReminder(mark)}[limit approach] This task has used about ${percent}% of its ${axis}. ` + `Start converging: finish the step you are on, drop work that is not required for the deliverable, and ` + `avoid starting anything you cannot complete within what is left.</system-reminder>` : `${openSystemReminder(mark)}[limit approach] About ${percent}% of the ${axis} is gone. Deliver now: ` + `stop exploring, write up what you have — including what is incomplete and what you would do next — ` + `and end the task. Work that is not reported before the allowance runs out is lost.</system-reminder>`; } export function resolveMaxTurns(limits) { return limits?.maxTurns; } export const ENV_DUE_GOVERNANCE_READ_BUDGET_MS = 5_000; const MAX_TIMER_DELAY_MS = 2_147_483_647; const DEADLINE_TICK = Symbol("deadline-tick"); export const GOVERNANCE_READ_STALLED = Symbol("governance-read-stalled"); const CHARGE_SETTLE_DISCLOSE_MS = 10_000; export async function awaitChargeWithSlowDisclosure(charge, onSlow, discloseAfterMs = CHARGE_SETTLE_DISCLOSE_MS) { let timer = setTimeout(() => { timer = undefined; try { onSlow(); } catch { } }, discloseAfterMs); try { return await charge; } finally { if (timer !== undefined) clearTimeout(timer); } } export async function raceUntilDeadline(p, deadline) { for (let firstPass = true;; firstPass = false) { const remaining = deadline - Date.now(); if (remaining <= 0 && !firstPass) return GOVERNANCE_READ_STALLED; let timer; const tick = new Promise((res) => { timer = setTimeout(() => res(DEADLINE_TICK), Math.max(0, Math.min(remaining, MAX_TIMER_DELAY_MS))); }); const out = await Promise.race([p, tick]).finally(() => clearTimeout(timer)); if (out !== DEADLINE_TICK) return out; } } export function platformLimitTerminal(reason, retryAfterMs, moment = "turn_boundary") { const message = moment === "entry" ? `a deployment usage window is exhausted (RunnerDeps.usageWindows), so the task was refused before its first model call — nothing ran and nothing was spent. There is no checkpoint to suspend into at this point, so the caller re-submits after the window frees, in ${String(retryAfterMs ?? 0)}ms.` : moment === "resume" ? `a deployment usage window is exhausted (RunnerDeps.usageWindows), so the resumed leg was stopped BEFORE its first model call — nothing was delivered or executed on this leg. The window frees in ${String(retryAfterMs ?? 0)}ms.` : reason === "env_lifetime" ? "the execution environment's declared lifetime is expiring and the run could not be suspended durably (not eligible, or the suspend was refused — see remoteEnvFailures / onError). It was stopped at a clean turn boundary instead of being killed mid-turn when the platform reclaims the environment." : `a deployment usage window is exhausted (RunnerDeps.usageWindows) and the run could not be suspended durably, so it was stopped at a clean turn boundary. The window frees in ${String(retryAfterMs ?? 0)}ms.`; const e = new Error(message); e.code = reason === "env_lifetime" ? "env.lifetime_expired" : "usage.window_exhausted"; if (retryAfterMs !== undefined) e.retryAfterMs = retryAfterMs; return e; } const disclosedUnevaluableWindow = new WeakSet(); export function discloseUnevaluableWindow(u, prepared, tracer, taskId, onError) { if (disclosedUnevaluableWindow.has(prepared)) return; disclosedUnevaluableWindow.add(prepared); emitTrace(tracer, () => ({ kind: "compaction.unevaluable", version: 1, taskId, estTokens: u.estTokens, ts: Date.now() })); const modelClause = u.modelId !== undefined ? ` "${u.modelId}"` : ""; const cause = u.windowValue !== undefined ? `its \`${u.windowField}\` is ${u.windowValue}, which is not a positive number of tokens — that field takes precedence over the ` + `others, so fixing or removing it is what restores the threshold` : `it declares no context window at all (\`autoCompactTokens\` / \`contextTokens\` / \`contextWindow\` are all absent) — declare one to restore the threshold`; try { onError?.(new Error(`auto-compaction cannot run for model${modelClause}: ${cause}. Until then there is no threshold to compare against, so the ` + `context will grow until the provider refuses the request. The conversation is already about ${u.estTokens} estimated tokens.`), { phase: "config", sessionId: prepared.sessionId }); } catch { } } const WALLTIME_SUSPEND_GRACE_SEC = 120; export const TIMER_LATENESS_REPORT_MS = 5_000; export function startTimeout(harness, abortController, remainingMs, softSuspendable = false) { const state = { fired: false, clear: () => { } }; if (remainingMs !== undefined) { const hardMs = Math.max(0, remainingMs + (softSuspendable ? WALLTIME_SUSPEND_GRACE_SEC * 1000 : 0)); const scheduledAtMs = Date.now() + hardMs; let leftMs = hardMs; let timer; const armChunk = () => { const chunk = Math.max(0, Math.min(leftMs, MAX_TIMER_DELAY_MS)); timer = setTimeout(() => { leftMs -= chunk; if (leftMs > 0) { armChunk(); return; } fireHardAbort(); }, chunk); }; const fireHardAbort = () => { if (state.fired) return; state.fired = true; state.latenessMs = Math.max(0, Date.now() - scheduledAtMs); abortController.abort(); void harness.abort(); }; armChunk(); state.clear = () => clearTimeout(timer); } return state; }