openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
151 lines (150 loc) • 6.23 kB
JavaScript
import { t as DEFAULT_AGENT_WORKSPACE_DIR } from "./workspace-default-DPT1Dhad.js";
import { t as createSubsystemLogger } from "./subsystem-Dy2tqXOS.js";
import { t as listRecommendedToolInstalls } from "./recommended-tool-installs-Bwv75ix-.js";
import { t as resolveSetupInferenceCandidateBrandId } from "./setup-inference-brand-vm6tI0LG.js";
import { o as detectAmbientInferenceBackends } from "./onboard-inference-ambient-BvxNJ30C.js";
import { fileURLToPath, pathToFileURL } from "node:url";
import path from "node:path";
import { Worker } from "node:worker_threads";
//#region src/system-agent/setup-inference-detection.ts
const SETUP_INFERENCE_DETECTION_TIMEOUT_MS = 3e4;
const log = createSubsystemLogger("system-agent/setup-inference-detection");
var SetupInferenceDetectionTimeoutError = class extends Error {
constructor(timeoutMs) {
super(`AI access detection did not finish after ${timeoutMs / 1e3}s. This Gateway may still be checking — try again.`);
this.name = "SetupInferenceDetectionTimeoutError";
}
};
let inFlightDetection;
let workerShutdown;
function trackWorkerShutdown(worker) {
const current = worker.terminate().then(() => void 0, (error) => {
log.warn(`Setup inference detection worker termination failed: ${String(error)}`);
});
workerShutdown = current;
current.finally(() => {
if (workerShutdown === current) workerShutdown = void 0;
});
}
function resolveDetectionWorkerUrl(currentModuleUrl = import.meta.url) {
const currentPath = fileURLToPath(currentModuleUrl);
const distIndex = currentPath.replaceAll(path.sep, "/").lastIndexOf("/dist/");
if (distIndex >= 0) {
const distRoot = currentPath.slice(0, distIndex + 6);
return pathToFileURL(path.join(distRoot, "system-agent", "setup-inference-detection.worker.js"));
}
const extension = path.extname(currentPath) || ".js";
return new URL(`./setup-inference-detection.worker${extension}`, currentModuleUrl);
}
function parseDetectionWorkerMessage(value) {
if (!value || typeof value !== "object" || Array.isArray(value)) return;
const message = value;
if ((message.type === "partial" || message.type === "result") && message.detection && typeof message.detection === "object") return message;
if (message.ok === false && typeof message.error === "string") return message;
}
function withAmbientCandidates(detection, env) {
const existing = new Set(detection.candidates.map((candidate) => `${candidate.kind}\0${candidate.modelRef}`));
const ambient = detectAmbientInferenceBackends(env).filter((candidate) => !existing.has(`${candidate.kind}\0${candidate.modelRef}`)).map((candidate) => {
const brandId = resolveSetupInferenceCandidateBrandId(candidate);
return Object.assign(candidate, brandId ? { brandId } : {}, { recommended: false });
});
if (ambient.length === 0) return detection;
return {
...detection,
candidates: [...detection.candidates, ...ambient]
};
}
function createUndetectedFallback() {
return {
candidates: [],
unavailableCandidates: [],
manualProviders: [],
authOptions: [],
recommendedInstalls: listRecommendedToolInstalls(),
workspace: DEFAULT_AGENT_WORKSPACE_DIR,
setupComplete: false
};
}
async function runDetectionWorker(options = {}) {
const workerUrl = options.workerUrl ?? resolveDetectionWorkerUrl();
const execArgv = workerUrl.pathname.endsWith(".ts") ? ["--import", "tsx"] : void 0;
const worker = new Worker(workerUrl, {
execArgv,
...options.workerData === void 0 ? options.agentId ? { workerData: { agentId: options.agentId } } : {} : { workerData: options.workerData }
});
const timeoutMs = options.timeoutMs ?? SETUP_INFERENCE_DETECTION_TIMEOUT_MS;
return await new Promise((resolve, reject) => {
let settled = false;
let partialDetection;
const settle = (finish) => {
if (settled) return;
settled = true;
clearTimeout(timer);
worker.removeAllListeners();
worker.on("error", () => void 0);
trackWorkerShutdown(worker);
finish();
};
worker.on("message", (value) => {
const message = parseDetectionWorkerMessage(value);
if (message && "type" in message && message.type === "partial") {
partialDetection = message.detection;
return;
}
settle(() => {
if (!message) {
reject(/* @__PURE__ */ new Error("setup inference detection worker returned an invalid result"));
return;
}
if ("ok" in message) {
reject(new Error(message.error));
return;
}
resolve(message.detection);
});
});
worker.once("error", (error) => settle(() => reject(error instanceof Error ? error : new Error(String(error)))));
worker.once("exit", (code) => {
if (code !== 0) settle(() => reject(/* @__PURE__ */ new Error(`setup inference detection worker exited with code ${code}`)));
else settle(() => reject(/* @__PURE__ */ new Error("setup inference detection worker exited without results")));
});
const timer = setTimeout(() => {
settle(() => {
log.warn(`Setup inference detection timed out after ${timeoutMs}ms; using partial signal if available.`);
const env = options.fallbackEnv ?? process.env;
const detection = withAmbientCandidates(partialDetection ?? createUndetectedFallback(), env);
if (detection.candidates.length > 0 || detection.unavailableCandidates.length > 0) {
resolve(detection);
return;
}
reject(new SetupInferenceDetectionTimeoutError(timeoutMs));
});
}, timeoutMs);
worker.unref();
});
}
/** Coalesce read-only detection and isolate native/plugin discovery from Gateway liveness. */
async function detectSetupInferenceIsolated(options = {}) {
const agentId = options.agentId?.trim() || void 0;
if (inFlightDetection) {
if (inFlightDetection.agentId === agentId) return await inFlightDetection.promise;
await inFlightDetection.promise.catch(() => void 0);
return await detectSetupInferenceIsolated(options);
}
if (workerShutdown) {
await workerShutdown;
return await detectSetupInferenceIsolated(options);
}
const current = runDetectionWorker(options);
inFlightDetection = {
agentId,
promise: current
};
try {
return await current;
} finally {
if (inFlightDetection?.promise === current) inFlightDetection = void 0;
}
}
//#endregion
export { detectSetupInferenceIsolated };