292 lines · 11.0 KB · JavaScript
RawDownload
import { access, readFile } from "node:fs/promises";import { createRequire } from "node:module";import { homedir } from "node:os";import { isAbsolute, resolve } from "node:path";import { fileURLToPath, pathToFileURL } from "node:url";import {  createFxAgent as createWasmAgent,  createFxTerminal as createWasmTerminal,  encodeXtermKeyEvent,  fxSdkApiVersion,  supportsJspi,  xtermAdapter,} from "./fx-sdk.js"; export { encodeXtermKeyEvent, fxSdkApiVersion, supportsJspi, xtermAdapter };export const libfxApiVersion = 2; const fetchOperationStale = 0;const fetchOperationApplied = 1;const fetchOperationBackpressure = 2; const require = createRequire(import.meta.url);const defaultCoreWasm = new URL("./fx-core.wasm", import.meta.url);const defaultTermWasm = new URL("./fx-term.wasm", import.meta.url);const defaultNativeCandidates = [  "./libfx.node",  `./libfx.${process.platform}-${process.arch}.node`,];let nativeBackendPromise; function jspiFallbackError(surface, nativeError) {  const nativeDetail = nativeError ? ` Native loading failed: ${nativeError.message}.` : " No compatible native addon was found.";  const error = new Error(    `libfx could not start the ${surface} backend.${nativeDetail} ` +    "The WebAssembly fallback requires JavaScript Promise Integration (JSPI). " +    "Run Node with --experimental-wasm-jspi or install a libfx package containing a compatible native addon.",  );  error.code = "LIBFX_JSPI_REQUIRED";  error.cause = nativeError;  return error;} async function loadNativeCandidate(candidate) {  if (candidate == null) return null;  if (candidate instanceof URL) {    if (candidate.protocol === "file:" && candidate.pathname.endsWith(".node")) {      return require(fileURLToPath(candidate));    }    const imported = await import(candidate.href);    return imported.default ?? imported;  }  if (typeof candidate === "object") return candidate.default ?? candidate;  if (typeof candidate !== "string") {    throw new TypeError("nativeAddon must be a module, path, URL, false, or undefined");  }  if (candidate.endsWith(".node")) return require(isAbsolute(candidate) ? candidate : resolve(candidate));  const imported = await import(candidate.startsWith("file:") ? candidate : pathToFileURL(candidate).href);  return imported.default ?? imported;} function validateNativeBackend(backend) {  if (!backend) return null;  const hasLowLevelCore = typeof backend.createCore === "function";  if ((hasLowLevelCore && backend.libfxApiVersion !== libfxApiVersion) ||    (!hasLowLevelCore && backend.libfxApiVersion !== undefined && backend.libfxApiVersion !== libfxApiVersion)) {    const actualVersion = backend.libfxApiVersion ?? "missing";    throw new Error(`native addon API version ${actualVersion} is incompatible with libfx API version ${libfxApiVersion}`);  }  if (typeof backend.createFxAgent !== "function" && typeof backend.createCore !== "function" &&    typeof backend.createFxTerminal !== "function") {    throw new Error("native addon must export createFxAgent(), createCore(), or createFxTerminal()");  }  return backend;} async function discoverNativeBackend() {  for (const relativePath of defaultNativeCandidates) {    const url = new URL(relativePath, import.meta.url);    try {      await access(fileURLToPath(url));    } catch (error) {      if (error?.code === "ENOENT") continue;      return { backend: null, error };    }    try {      return { backend: validateNativeBackend(await loadNativeCandidate(url)), error: null };    } catch (error) {      return { backend: null, error };    }  }  return { backend: null, error: null };} async function resolveNativeBackend(nativeAddon) {  if (nativeAddon === false) return { backend: null, error: null };  if (nativeAddon !== undefined) {    try {      return { backend: validateNativeBackend(await loadNativeCandidate(nativeAddon)), error: null };    } catch (error) {      return { backend: null, error };    }  }  nativeBackendPromise ??= discoverNativeBackend();  return nativeBackendPromise;} async function wasmBytes(input) {  if (input instanceof URL && input.protocol === "file:") return readFile(input);  if (typeof input === "string" && !URL.canParse(input)) return readFile(input);  return input;} function validateGatewayChatUrl(value) {  if (value === undefined) return;  if (typeof value !== "string") throw new TypeError("FX_GATEWAY_CHAT_URL must be a string");  let url;  try { url = new URL(value); } catch { throw new TypeError("FX_GATEWAY_CHAT_URL must be a valid URL"); }  if (url.username || url.password || url.hash) {    throw new TypeError("FX_GATEWAY_CHAT_URL must not contain credentials or a fragment");  }  if (url.href === "https://ai-gateway.vercel.sh/v3/ai/language-model") return;  const loopback = url.hostname === "127.0.0.1" || url.hostname === "[::1]" || url.hostname === "localhost";  if (url.protocol !== "http:" || !loopback || !url.port) {    throw new TypeError("FX_GATEWAY_CHAT_URL must use the canonical Gateway or explicit loopback HTTP");  }} function createNativeCoreRuntime(addon, options) {  const apiKey = options.env?.AI_GATEWAY_API_KEY;  const model = options.env?.FX_MODEL;  const gatewayChatUrl = options.env?.FX_GATEWAY_CHAT_URL;  validateGatewayChatUrl(gatewayChatUrl);  const core = addon.createCore({    apiKey,    home: options.home ?? homedir(),    workspaceRoot: options.workspaceRoot ?? process.cwd(),    ...(model === undefined ? {} : { model }),    ...(gatewayChatUrl === undefined ? {} : { gatewayChatUrl }),  });  let exitedResolve;  let lineHandler = null;  let lineBuffer = "";  let settled = false;  let fetchState = null;  const exited = new Promise((resolve) => { exitedResolve = resolve; });  const abortHostEffects = () => {    fetchState?.controller.abort();    try { addon.abortCoreFetch(core); } catch {}  };  const finish = (code) => {    if (settled) return;    settled = true;    clearInterval(timer);    abortHostEffects();    try { addon.destroyCore(core); } catch {}    exitedResolve(code);  };  const pumpFetch = async (request) => {    const controller = new AbortController();    const state = { handle: request.handle, controller };    fetchState = state;    try {      const response = await (options.fetch ?? globalThis.fetch)(request.url, {        method: request.method,        headers: new Headers(JSON.parse(request.headers).map(({ name, value }) => [name, value])),        body: request.body?.length ? Buffer.from(request.body, "base64") : undefined,        signal: controller.signal,      });      const started = addon.startCoreFetchResponse(core, state.handle, response.status);      if (started === fetchOperationStale) return;      if (started !== fetchOperationApplied) throw new Error(`invalid native fetch start result ${started}`);      if (response.body) {        for await (const chunk of response.body) {          const buffer = Buffer.from(chunk);          let offset = 0;          while (offset < buffer.length) {            const end = Math.min(offset + 64 * 1024, buffer.length);            const pushed = addon.pushCoreFetchResponse(core, state.handle, buffer.subarray(offset, end));            if (pushed === fetchOperationApplied) {              offset = end;              continue;            }            if (pushed === fetchOperationStale) return;            if (pushed !== fetchOperationBackpressure) throw new Error(`invalid native fetch push result ${pushed}`);            await new Promise((resolve) => setTimeout(resolve, 2));          }        }      }      const finished = addon.finishCoreFetch(core, state.handle);      if (finished !== fetchOperationApplied && finished !== fetchOperationStale) {        throw new Error(`invalid native fetch finish result ${finished}`);      }    } catch (error) {      if (error?.name !== "AbortError" || !controller.signal.aborted) {        try {          if (addon.coreFetchActive(core, state.handle)) addon.failCoreFetch(core, state.handle);        } catch {}      }    } finally {      if (fetchState === state) fetchState = null;    }  };  const timer = setInterval(() => {    try {      if (fetchState) {        if (!fetchState.controller.signal.aborted && !addon.coreFetchActive(core, fetchState.handle)) {          fetchState.controller.abort();        }      } else {        const fetchRequest = addon.takeCoreFetch(core);        if (fetchRequest) void pumpFetch(JSON.parse(fetchRequest.toString("utf8")));      }      const chunk = addon.drainCore(core);      if (chunk.length && lineHandler) {        lineBuffer += chunk.toString("utf8");        for (;;) {          const newline = lineBuffer.indexOf("\n");          if (newline < 0) break;          const line = lineBuffer.slice(0, newline);          lineBuffer = lineBuffer.slice(newline + 1);          if (line) lineHandler(JSON.parse(line));        }      }      if (addon.coreExited(core)) finish(addon.coreExitCode(core));    } catch {      finish(1);    }  }, 2);   return {    exited,    write(data) { addon.writeCore(core, Buffer.from(data)); },    closeStdin() { addon.closeCore(core); },    abortHostEffects,    abort() { abortHostEffects(); addon.closeCore(core); },    setLineHandler(handler) { lineHandler = handler; },  };} function createNativeAgent(addon, options) {  return createWasmAgent({    ...options,    runtimeFactory(runtimeOptions) {      return createNativeCoreRuntime(addon, runtimeOptions);    },  });} async function createWithFallback(surface, nativeMethod, wasmFactory, defaultWasm, options) {  const { nativeAddon, backend = "auto", ...runtimeOptions } = options ?? {};  validateGatewayChatUrl(runtimeOptions.env?.FX_GATEWAY_CHAT_URL);  if (!new Set(["auto", "native", "wasm"]).has(backend)) {    throw new TypeError('backend must be "auto", "native", or "wasm"');  }   let nativeError;  if (backend !== "wasm") {    const native = await resolveNativeBackend(nativeAddon);    nativeError = native.error;    if (typeof native.backend?.[nativeMethod] === "function" ||      (surface === "agent" && typeof native.backend?.createCore === "function")) {      try {        if (typeof native.backend?.[nativeMethod] === "function") {          return await native.backend[nativeMethod](runtimeOptions);        }        return await createNativeAgent(native.backend, runtimeOptions);      } catch (error) {        nativeError = error;        if (backend === "native") throw error;      }    }    if (backend === "native") {      const error = nativeError ?? new Error(`native addon does not provide ${nativeMethod}()`);      error.code ??= "LIBFX_NATIVE_UNAVAILABLE";      throw error;    }  }   if (!supportsJspi()) throw jspiFallbackError(surface, nativeError);  return wasmFactory({    ...runtimeOptions,    wasm: await wasmBytes(runtimeOptions.wasm ?? defaultWasm),  });} export function createFxAgent(options = {}) {  return createWithFallback("agent", "createFxAgent", createWasmAgent, defaultCoreWasm, options);} export function createFxTerminal(options = {}) {  return createWithFallback("terminal", "createFxTerminal", createWasmTerminal, defaultTermWasm, options);}