@sema-agent/core
Version:
Stateless, task-oriented AI agent core
69 lines (68 loc) • 3.59 kB
JavaScript
import vm from "node:vm";
import { splitWorkflowMeta, WorkflowScriptError } from "./workflow-meta.js";
import { thrownText } from "../core/thrown-value.js";
export const WORKFLOW_DATE_ERR = "Date.now() / new Date() are unavailable in workflow scripts (breaks resume). Stamp results after the workflow returns, or pass timestamps via args.";
export const WORKFLOW_RANDOM_ERR = "Math.random() is unavailable in workflow scripts (breaks resume). For N independent samples, include the index in the agent label or prompt.";
const DETERMINISM_SHIM = `(() => {
const NOW_ERR = ${JSON.stringify(WORKFLOW_DATE_ERR)};
const RANDOM_ERR = ${JSON.stringify(WORKFLOW_RANDOM_ERR)};
Math.random = function random() { throw new Error(RANDOM_ERR) };
const RealDate = Date;
RealDate.now = function now() { throw new Error(NOW_ERR) };
function ShimDate(...a) {
if (!new.target) throw new Error(NOW_ERR); // bare Date() → now-string
if (a.length === 0) throw new Error(NOW_ERR);
return Reflect.construct(RealDate, a, new.target);
}
ShimDate.now = RealDate.now;
ShimDate.parse = RealDate.parse;
ShimDate.UTC = RealDate.UTC;
ShimDate.prototype = RealDate.prototype;
// Close the (new Date(x)).constructor backdoor to RealDate.now — point
// .constructor at the shim, then freeze RealDate so it can't be undone.
RealDate.prototype.constructor = ShimDate;
Object.freeze(RealDate);
globalThis.Date = ShimDate;
})()`;
export const devWorkflowScriptRunner = {
safeForUntrustedScripts: false,
async run({ scriptSource, primitives, scriptArgs, signal }) {
if (signal?.aborted)
throw new Error("workflow script aborted before start");
const { meta, body } = splitWorkflowMeta(scriptSource);
const wrapped = `(async function(agent, parallel, pipeline, phase, log, budget, args) {\n${body}\n})`;
const importThrow = () => {
throw new WorkflowScriptError("import() is not available in workflow scripts.");
};
let compiled;
try {
try {
compiled = new vm.Script(wrapped, { filename: "workflow-script.js", importModuleDynamically: importThrow });
}
catch (err) {
if (err instanceof SyntaxError)
throw err;
compiled = new vm.Script(wrapped, { filename: "workflow-script.js" });
}
}
catch (err) {
throw new WorkflowScriptError(`workflow script failed to compile: ${thrownText(err)}`);
}
const noHostApi = (name) => () => {
throw new WorkflowScriptError(`${name} is not available in workflow scripts — there are no Node/host APIs here. Use agent()/log()/` +
`budget for work, and schedule time-based behavior outside the workflow.`);
};
const context = vm.createContext({
require: noHostApi("require"),
setTimeout: noHostApi("setTimeout"),
setInterval: noHostApi("setInterval"),
setImmediate: noHostApi("setImmediate"),
fetch: noHostApi("fetch"),
queueMicrotask: noHostApi("queueMicrotask"),
}, { codeGeneration: { strings: false, wasm: false } });
new vm.Script(DETERMINISM_SHIM, { filename: "workflow-determinism-shim.js" }).runInContext(context);
const fn = compiled.runInContext(context);
const result = await fn(primitives.agent, primitives.parallel, primitives.pipeline, primitives.phase, primitives.log, primitives.budget, scriptArgs);
return { result, meta };
},
};