UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

338 lines (335 loc) • 15 kB
import { c as isRecord } from "../record-coerce-TIsquoVR.mjs"; import { n as serveWorkerTasks } from "../worker-task-server-BC_7U18u.mjs"; import { a as normalizeSourceStack, i as buildUserSource, o as CODE_MODE_CONTROLLER_SOURCE, r as USER_SOURCE_FILE, t as prepareSource } from "../code-mode-source-f8CZaGjY.mjs"; import { a as captureCodeModeValue, i as captureCodeModeOutput, n as EMPTY_CODE_MODE_OUTPUT, r as boundCodeModeError } from "../code-mode-json-DLneEPkt.mjs"; import { t as CodeModeNodeProgress } from "../code-mode-node-progress-DlOYoVlG.mjs"; import { n as ToolInputError } from "../tool-input-error-mjW74R8m.mjs"; import { getHeapStatistics } from "node:v8"; import { types } from "node:util"; import { setImmediate } from "node:timers/promises"; import { Script, createContext } from "node:vm"; //#region src/agents/code-mode-node.worker.ts let cell; process.on("unhandledRejection", (reason, promise) => cell?.rejections.set(promise, reason)); process.on("rejectionHandled", (promise) => cell?.rejections.delete(promise)); const bridgeMethods = /* @__PURE__ */ new Set([ "search", "describe", "callValue", "resultSave", "resultLoad", "resultDelete", "nodes", "yield", "namespace", "agentSpawn", "agentWait", "skillsList", "skillsSearch", "skillsRead", "sleep", "swarmNote" ]); function isBridgeMethod(method) { return bridgeMethods.has(method); } const initializeScript = new Script(String.raw` (() => { // Keep native encoding prototypes private when this worker is reused. const Encoder = globalThis.__openclawNodeTextEncoder; const Decoder = globalThis.__openclawNodeTextDecoder; const Bytes = Uint8Array; delete globalThis.__openclawNodeTextEncoder; delete globalThis.__openclawNodeTextDecoder; Object.defineProperties(globalThis, { TextEncoder: { value: class TextEncoder { #encoder = new Encoder(); get encoding() { return this.#encoder.encoding; } encode(input) { return new Bytes(this.#encoder.encode(input)); } encodeInto(input, destination) { const { read, written } = this.#encoder.encodeInto(input, destination); return { read, written }; } }, enumerable: true }, TextDecoder: { value: class TextDecoder { #decoder; constructor(label, options) { this.#decoder = new Decoder(label, options); } get encoding() { return this.#decoder.encoding; } get fatal() { return this.#decoder.fatal; } get ignoreBOM() { return this.#decoder.ignoreBOM; } decode(input, options) { return this.#decoder.decode(input, options); } }, enumerable: true }, }); })(); Object.assign(globalThis, JSON.parse(__openclawNodeInit)); delete globalThis.__openclawNodeInit; ${CODE_MODE_CONTROLLER_SOURCE} (() => { const finish = globalThis.__openclawNodeFinish; delete globalThis.__openclawNodeFinish; const stringify = JSON.stringify; const string = String; const encodeError = (error) => { const bridgeError = __openclawIsBridgeError(error); const diagnostic = (read, fallback) => { try { return read(); } catch { return fallback; } }; // Guest error properties may throw; recorded bridge identity must still reach finish. const stack = diagnostic(() => error?.stack, ""); // Provenance records contain primitives and never inherit guest toJSON hooks. return stringify({ __proto__: null, bridgeError, name: diagnostic(() => string(error?.name ?? "Error"), "Error"), message: diagnostic(() => string(error?.message ?? error), "Error"), stack: typeof stack === "string" ? stack : "", }); }; Object.defineProperties(globalThis, { __openclawNodeEncodeError: { value: encodeError }, __openclawNodeObserveResult: { value: (result) => { result.then(value => finish(true, value), error => finish(false, encodeError(error))); }}, }); })(); `, { filename: "openclaw-code-mode:controller.js" }); const settleScript = new Script("for (const reply of JSON.parse(__openclawNodeReplies)) __openclawSettleBridge(reply.id, reply.ok, reply.json); delete globalThis.__openclawNodeReplies;", { filename: "openclaw-code-mode:controller.js" }); const drainScript = new Script(`(() => { const error = __openclawAdmissionError(); if (!error) __openclawDrainQueuedRequests(); return error; })()`, { filename: "openclaw-code-mode:controller.js" }); const outputScript = new Script("__openclawTakeOutputJson()", { filename: "openclaw-code-mode:controller.js" }); const observeResultScript = new Script("__openclawNodeObserveResult(__openclawResult)", { filename: "openclaw-code-mode:controller.js" }); const rejectionScript = new Script(`(() => { const error = __openclawNodeRejection; delete globalThis.__openclawNodeRejection; return __openclawNodeEncodeError(error); })()`, { filename: "openclaw-code-mode:controller.js" }); function evaluate(current, script) { if (current.deadline - performance.now() <= 0) throw new Error("code mode timeout exceeded"); return script.runInContext(current.context); } function sourceFrames(stack, location) { return normalizeSourceStack(stack, location)?.split("\n").filter((line) => line.includes("openclaw-code-mode:user.js") && /^\s+at /u.test(line)) ?? []; } function guestError(error, location) { if (!types.isNativeError(error)) return String(error); const frames = sourceFrames(error.stack, location); if (frames.length === 0 && error.name === "SyntaxError") { const syntax = /^openclaw-code-mode:user\.js:(\d+)\n[^\n]*\n([ \t]*)\^/u.exec(error.stack ?? ""); const line = syntax?.[1]; const prefix = syntax?.[2]; if (line !== void 0 && prefix !== void 0) frames.push(...sourceFrames(` at ${USER_SOURCE_FILE}:${line}:${prefix.length + 1}`, location)); } return [`${error.name}: ${error.message}`, ...frames].join("\n"); } function createCell(input, source, startedAt, progress, consumed) { const preparedAt = performance.now(); const deadline = Math.min(startedAt + input.config.timeoutMs, preparedAt + (input.executionTimeoutMs ?? Infinity)); if (deadline <= preparedAt) throw new Error("code mode timeout exceeded"); const program = buildUserSource(source, input.prelude, "utf16"); const context = createContext(Object.create(null), { microtaskMode: "afterEvaluate" }); const current = { context, config: input.config, location: program.location, pendingRequests: [], canceledRequestIds: [], rejections: /* @__PURE__ */ new Map(), deadline, progress }; cell = current; context["__openclawNodeTextEncoder"] = TextEncoder; context["__openclawNodeTextDecoder"] = TextDecoder; context["__openclawHostRequest"] = (method, argsJson, id, stack) => { if (current.pendingRequests.length >= current.config.maxPendingToolCalls) { current.admissionError = "too many pending code mode tool calls"; throw new Error(current.admissionError); } if (!isBridgeMethod(method)) throw new Error("unsupported code mode bridge method"); const args = JSON.parse(argsJson); if (!Array.isArray(args)) throw new Error("invalid code mode bridge arguments: expected an array"); if (!id.startsWith(`bridge:${method}:`) || !/^bridge:[A-Za-z]+:[1-9]\d*$/u.test(id)) throw new Error("invalid code mode bridge id"); if (current.pendingRequests.some((request) => request.id === id)) throw new Error("duplicate code mode bridge id"); current.pendingRequests.push({ id, method, args }); return sourceFrames(typeof stack === "string" ? stack.slice(0, 8192) : "", current.location).join("\n"); }; context["__openclawHostCancelRequest"] = (id) => { const index = current.pendingRequests.findIndex((request) => request.id === id); if (index >= 0) { current.pendingRequests.splice(index, 1); current.canceledRequestIds.push(id); } }; context["__openclawHostObserveNetworkContent"] = () => { current.networkContentObserved = true; current.progress.observeNetworkContent(); }; context["__openclawHostOutput"] = (json) => current.progress.append(json); context["__openclawNodeInit"] = JSON.stringify({ __openclawCatalog: input.catalog, __openclawNamespaces: input.namespaces, __openclawApiFiles: input.apiFiles ?? [], __openclawSwarmEnabled: input.swarmEnabled === true, __openclawMaxPendingToolCalls: input.config.maxPendingToolCalls }); context["__openclawNodeFinish"] = (ok, json) => { current.outcome = { ok, json }; }; progress.deadline = performance.timeOrigin + deadline; consumed?.(); evaluate(current, initializeScript); evaluate(current, new Script(program.source, { filename: USER_SOURCE_FILE })); evaluate(current, observeResultScript); return current; } function settle(current, requests) { current.context["__openclawNodeReplies"] = JSON.stringify(requests); try { evaluate(current, settleScript); } finally { for (const request of requests) request.json = ""; requests.length = 0; } } function takeOutput(current) { const json = evaluate(current, outputScript); return JSON.parse(String(json)); } function formatGuestFailure(current, json) { const value = JSON.parse(json); if (!value.bridgeError && value.name === "ReferenceError" && /^(?:require|module|process) is not defined$/u.test(value.message)) return { code: "invalid_input", error: "code mode module access is disabled." }; return { code: "internal_error", ...value.bridgeError ? { failurePhase: "bridge" } : {}, error: [`${value.name}: ${value.message}`, ...sourceFrames(value.stack, current.location)].join("\n") }; } function failed(code, error, output = EMPTY_CODE_MODE_OUTPUT, failurePhase) { return { status: "failed", code, error, output, failurePhase: failurePhase ?? (code === "invalid_input" ? "input" : "guest"), bridgeDispatchStarted: false }; } async function run(input, channel) { let output = []; let consumed = channel?.consumeInput; const config = input.config; const startedAt = performance.now(); const progress = new CodeModeNodeProgress(input.progress); try { const current = input.kind === "exec" ? createCell(input, prepareSource(input.source), startedAt, progress, consumed) : cell; if (!current) throw new Error("code mode continuation is no longer available"); if (input.kind === "resume") { current.progress = progress; current.config = config; current.deadline = performance.now() + config.timeoutMs; progress.deadline = performance.timeOrigin + current.deadline; if (current.networkContentObserved) progress.observeNetworkContent(); current.pendingRequests = input.pendingRequests ?? []; current.canceledRequestIds = []; settle(current, input.settledRequests); } else consumed = void 0; for (;;) { consumed?.(); consumed = void 0; const admissionError = current.admissionError ?? evaluate(current, drainScript); if (admissionError !== void 0 && typeof admissionError !== "string") throw new Error("invalid code mode admission error"); if (admissionError) throw new ToolInputError(admissionError); await setImmediate(); output = takeOutput(current); const pending = !current.outcome; if (pending && current.pendingRequests.length === 0) throw new Error("code mode promise is pending without host work"); if (pending || current.pendingRequests.length > 0) { const settlementMode = pending ? { kind: "awaiting" } : { kind: "draining", requiredRequestIds: current.pendingRequests.map((request) => request.id) }; const boundary = { pendingRequests: current.pendingRequests, canceledRequestIds: current.canceledRequestIds, settlementMode, output: captureCodeModeOutput(output, config.maxOutputBytes), memoryUsedBytes: getHeapStatistics().used_heap_size, ...current.networkContentObserved ? { networkContentObserved: true } : {} }; if (!channel || !input.inlineHost) return { status: "waiting", ...boundary, continuation: void 0 }; const response = await channel.request({ status: "boundary", ...boundary }); output = []; consumed = response.consumed; const command = response.input; if (command.kind === "checkpoint") { consumed(); consumed = void 0; return { status: "waiting", ...boundary, continuation: void 0, output: EMPTY_CODE_MODE_OUTPUT }; } if (command.kind !== "continue") throw new Error("invalid code mode continuation"); if (!Number.isFinite(command.timeoutMs) || command.timeoutMs <= 0 || command.timeoutMs > config.timeoutMs) throw new Error("code mode timeout exceeded"); current.deadline = performance.now() + command.timeoutMs; current.pendingRequests = command.pendingRequests; current.canceledRequestIds = []; settle(current, command.settledRequests); continue; } const outcome = current.outcome; if (!outcome.ok) { const failure = formatGuestFailure(current, outcome.json); return failed(failure.code, boundCodeModeError(failure.error, config.maxOutputBytes), captureCodeModeOutput(output, config.maxOutputBytes), failure.failurePhase); } if (current.rejections.size > 0) { current.context["__openclawNodeRejection"] = current.rejections.values().next().value; const encoded = evaluate(current, rejectionScript); const failure = formatGuestFailure(current, String(encoded)); return failed(failure.code, boundCodeModeError(failure.error, config.maxOutputBytes), captureCodeModeOutput(output, config.maxOutputBytes), failure.failurePhase); } return { status: "completed", output: captureCodeModeOutput(output, config.maxOutputBytes), value: captureCodeModeValue(JSON.parse(outcome.json), config.maxOutputBytes, input.retainFinalValue ? Math.min(config.memoryLimitBytes, config.maxSnapshotBytes) : config.maxOutputBytes) }; } } catch (error) { const timeout = types.isNativeError(error) && error.message === "code mode timeout exceeded"; if (cell && output.length === 0 && !timeout) try { output = takeOutput(cell); } catch {} return failed(timeout ? "timeout" : error instanceof ToolInputError ? "invalid_input" : "internal_error", boundCodeModeError(timeout ? "code mode timeout exceeded" : cell ? guestError(error, cell.location) : error instanceof Error ? error.message : String(error), config.maxOutputBytes), timeout ? progress.output() : captureCodeModeOutput(output, config.maxOutputBytes)); } } serveWorkerTasks(async (input, channel) => { if (!isRecord(input) || !isRecord(input.config) || input.kind !== "exec" && input.kind !== "resume") return failed("invalid_input", "invalid code mode worker input"); const result = await run(input, channel); if (cell?.networkContentObserved) result.networkContentObserved = true; if (result.status !== "waiting") cell = void 0; return result; }); //#endregion export {};