@sema-agent/core
Version:
Stateless, task-oriented AI agent core
119 lines (118 loc) • 6.34 kB
JavaScript
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;
}