@sema-agent/core
Version:
Stateless, task-oriented AI agent core
102 lines (101 loc) • 4.06 kB
JavaScript
import { traceHookCrash } from "./gate-crash-trace.js";
import { thrownText } from "./thrown-value.js";
export function gateWaitFailureOf(err) {
const mark = err?.gateWait;
return mark === "timeout" || mark === "aborted" ? mark : undefined;
}
function gateWaitError(message, kind) {
const err = new Error(message);
err.gateWait = kind;
return err;
}
export function withGateTimeout(p, ms, signal, label = "reversibilityProbe") {
if (ms === undefined && signal === undefined)
return p;
return new Promise((resolve, reject) => {
let settled = false;
const done = (fn, v) => {
if (settled)
return;
settled = true;
if (timer !== undefined)
clearTimeout(timer);
signal?.removeEventListener("abort", onAbort);
fn(v);
};
const onAbort = () => {
const reason = signal?.reason;
const ended = gateWaitFailureOf(reason) !== undefined
? reason
: reason?.name === "TimeoutError"
? gateWaitError(`${label} timed out (its deadline elapsed)`, "timeout")
: gateWaitError(`${label} aborted (task ended)`, "aborted");
done(reject, ended);
};
const timer = ms !== undefined
? setTimeout(() => done(reject, gateWaitError(`${label} timed out after ${ms}ms`, "timeout")), ms)
: undefined;
p.then((v) => done(resolve, v), (err) => done(reject, err));
if (signal?.aborted) {
onAbort();
return;
}
signal?.addEventListener("abort", onAbort);
});
}
export const DEFAULT_GATE_AWAIT_TIMEOUT_MS = 30_000;
export function createGateDeadline(input, notifier) {
let resolved;
return () => {
if (resolved !== undefined)
return resolved;
const supplied = input.approvalTimeoutMs;
if (supplied === undefined)
resolved = DEFAULT_GATE_AWAIT_TIMEOUT_MS;
else if (Number.isFinite(supplied) && supplied >= 0 && supplied <= 2_147_483_647)
resolved = supplied;
else {
resolved = DEFAULT_GATE_AWAIT_TIMEOUT_MS;
traceHookCrash(input, new Error(`approvalTimeoutMs must be a non-negative finite number no greater than 2147483647 (got ${String(supplied)}) — the gate's bounded awaits fall back to the ${DEFAULT_GATE_AWAIT_TIMEOUT_MS}ms default`), notifier);
}
return resolved;
};
}
export async function fromSource(ask, signal, label) {
try {
return { ok: true, value: await withGateTimeout(Promise.resolve().then(ask), signal === undefined ? DEFAULT_GATE_AWAIT_TIMEOUT_MS : undefined, signal, label) };
}
catch (cause) {
return { ok: false, cause };
}
}
export async function awaitWriteProtectionJudgment(judge, deadlineMs, signal, onFailure) {
const deadline = new AbortController();
const judged = judge(deadline.signal);
if (judged === null)
return undefined;
const ms = deadlineMs();
const net = new AbortController();
const endBoth = (reason) => {
if (!deadline.signal.aborted)
deadline.abort(reason);
setImmediate(() => net.abort(reason));
};
const timer = setTimeout(() => endBoth(gateWaitError(`writeProtectionJudge timed out after ${ms}ms`, "timeout")), ms);
const onTaskAbort = () => endBoth(gateWaitError("writeProtectionJudge aborted (task ended)", "aborted"));
if (signal?.aborted === true)
onTaskAbort();
else
signal?.addEventListener("abort", onTaskAbort);
try {
return await withGateTimeout(judged, undefined, net.signal, "writeProtectionJudge");
}
catch (err) {
onFailure?.(err);
return { kind: "unresolvable", cause: `the write-protection judgment did not answer: ${thrownText(err)}`, unresolvedSymlink: false };
}
finally {
clearTimeout(timer);
signal?.removeEventListener("abort", onTaskAbort);
}
}