import { k as toUnixPath, y as Debug, l as getErrorMsg, M as MIN_NODE_VERSION, x as createFilterByPattern, Q as ServerStorageName, U as SecretStorageKey, z as buildSuccessResponse, B as buildFailResponse, F as verifyZod } from './shared/gpt-runner-shared.15a86815.mjs'; import 'minimatch'; import 'debug'; import axios from 'axios'; import { HttpProxyAgent } from 'http-proxy-agent'; import { HttpsProxyAgent } from 'https-proxy-agent'; import { spawn } from 'node:child_process'; import fs, { promises } from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import getCacheDir from 'cachedir'; import { fileURLToPath } from 'node:url'; import 'jsonc-parser'; import launch from 'launch-editor'; import { kvsLocalStorage } from '@kvs/node-localstorage'; import open from 'open'; import fp from 'find-free-ports'; import ip from 'ip'; import { fetch, Headers, Request, Response } from 'undici'; import 'web-streams-polyfill/polyfill'; import 'zod-to-json-schema'; import 'zod'; function initAxios() { const httpProxyUrl = process.env.HTTP_PROXY; const httpsProxyUrl = process.env.HTTPS_PROXY; if (httpProxyUrl) { const httpAgent = new HttpProxyAgent(httpProxyUrl); axios.defaults.httpAgent = httpAgent; } if (httpsProxyUrl) { const httpsAgent = new HttpsProxyAgent(httpsProxyUrl); axios.defaults.httpsAgent = httpsAgent; } } let axiosInstance = null; function getAxiosInstance() { if (!axiosInstance) { initAxios(); axiosInstance = axios.create(); } return axiosInstance; } class PathUtils { static getCurrentDirName(importMetaUrl, getDirname) { let dirname = ""; try { dirname = getDirname(); } catch { } if (!importMetaUrl) return toUnixPath(dirname); const __filename = fileURLToPath(importMetaUrl); const __dirname = path.dirname(__filename); return __dirname; } static join(...paths) { return toUnixPath(path.join(...paths)); } static resolve(...paths) { return toUnixPath(path.resolve(...paths)); } static relative(from, to) { return toUnixPath(path.relative(from, to)); } static dirname(filePath) { return toUnixPath(path.dirname(filePath)); } static extname(filePath) { if (!PathUtils.isFile(filePath)) return ""; return path.extname(filePath); } static isFile(filePath) { return fs.existsSync(filePath) && fs.statSync(filePath).isFile(); } static isDirectory(filePath) { return fs.existsSync(filePath) && fs.statSync(filePath).isDirectory(); } static includeExt(filePath, exts) { if (!exts || !Array.isArray(exts)) return false; return exts.some((ext) => filePath.endsWith(ext)); } static isExit(filePath) { return fs.existsSync(filePath); } static isAccessible(filePath, mode) { if (!PathUtils.isExit(filePath)) return false; const modeMap = { F: fs.constants.F_OK, R: fs.constants.R_OK, W: fs.constants.W_OK, X: fs.constants.X_OK }; const finalMode = modeMap[mode || "F"] || fs.constants.F_OK; try { fs.accessSync(filePath, finalMode); return true; } catch (err) { return false; } } static getDirPath(filePath) { return path.dirname(filePath); } } async function getGlobalCacheDir(name) { const cacheDir = getCacheDir(name); await createCacheDir(cacheDir); return cacheDir; } async function createCacheDir(cacheDir) { if (await PathUtils.isAccessible(cacheDir, "W")) return; await fs.promises.mkdir(cacheDir, { recursive: true }); } const _BinaryDownloader = class { static async getBinaryPath() { const cacheDir = await getGlobalCacheDir("nicepkg-tunnel"); const binaryPath = path.join(cacheDir, _BinaryDownloader.BINARY_FILENAME); return binaryPath; } static async downloadBinary() { const debug = new Debug("tunnel"); const binaryPath = await _BinaryDownloader.getBinaryPath(); if (!fs.existsSync(binaryPath)) { const binaryUrl = `https://cdn-media.huggingface.co/frpc-gradio-${_BinaryDownloader.VERSION}/${_BinaryDownloader.BINARY_NAME}${_BinaryDownloader.EXTENSION}`; debug.log(`Downloading ${binaryUrl} to ${binaryPath}...`); try { const axios = getAxiosInstance(); const response = await axios.get(binaryUrl, { responseType: "arraybuffer" }); await fs.promises.writeFile(binaryPath, response.data, "binary"); fs.chmodSync(binaryPath, 493); debug.log(`Downloaded success ${binaryUrl} to ${binaryPath}`); } catch (err) { debug.error(`Failed to download ${binaryUrl}: ${err}`); if (err?.response?.status === 403) { throw new Error( `Cannot set up a share link as this platform is incompatible. Please create a GitHub issue with information about your platform: ${JSON.stringify( os.userInfo() )}` ); } throw err; } } } }; let BinaryDownloader = _BinaryDownloader; BinaryDownloader.VERSION = "0.2"; BinaryDownloader.EXTENSION = os.platform() === "win32" ? ".exe" : ""; BinaryDownloader.MACHINE = os.arch(); BinaryDownloader.ARCH = _BinaryDownloader.MACHINE === "x64" ? "amd64" : _BinaryDownloader.MACHINE; BinaryDownloader.BINARY_NAME = `frpc_${os.type().toLowerCase()}_${_BinaryDownloader.ARCH.toLowerCase()}`; BinaryDownloader.BINARY_FILENAME = `${_BinaryDownloader.BINARY_NAME}_v${_BinaryDownloader.VERSION}`; class TunnelProcess { constructor(command) { this.proc = null; this.command = command; } async start() { const binaryPath = await BinaryDownloader.getBinaryPath(); await BinaryDownloader.downloadBinary(); return this._startProcess(binaryPath); } kill() { if (this.proc !== null) { this.proc.kill("SIGTERM"); this.proc = null; } } _startProcess(binary) { return new Promise((resolve, reject) => { this.proc = spawn(binary, this.command, { stdio: ["ignore", "pipe", "pipe"] }); process.once("exit", () => this.kill()); let output = ""; this.proc.stdout.on("data", (data) => { output += data.toString(); const match = output.match(/start proxy success: (.+)/); if (match) { resolve(match[1]); output = ""; } }); this.proc.stderr.on("data", (data) => { output += data.toString(); }); this.proc.on("error", (err) => { reject(err); }); }); } } class Tunnel { constructor(options) { this.GRADIO_API_SERVER_URL = "https://api.gradio.app/v2/tunnel-request"; const { remoteHost, remotePort, localHost, localPort, shareToken } = options; this.url = null; this.remoteHost = remoteHost || ""; this.remotePort = remotePort || 0; this.localHost = localHost || "localhost"; this.localPort = localPort; this.shareToken = shareToken || ""; } async initProcess() { if (!this.remoteHost || !this.remotePort) { try { const axios = getAxiosInstance(); const res = await axios.get(this.GRADIO_API_SERVER_URL); const data = res.data; this.remoteHost = data[0].host; this.remotePort = data[0].port; } catch (err) { throw new Error(`Failed to fetch ${this.GRADIO_API_SERVER_URL}: ${getErrorMsg(err)}`); } } const command = [ "http", "-n", this.shareToken, "-l", `${this.localPort}`, "-i", this.localHost, "--uc", "--sd", "random", "--ue", "--server_addr", `${this.remoteHost}:${this.remotePort}`, "--disable_log_color" ]; this.tunnelProcess = new TunnelProcess(command); } async startTunnel() { if (!this.tunnelProcess) await this.initProcess(); this.url = await this.tunnelProcess.start(); return this.url; } kill() { if (this.tunnelProcess) { console.log( `Killing tunnel ${this.localHost}:${this.localPort} <> ${this.url}` ); this.tunnelProcess.kill(); } } } function compareVersion(a, b) { const aParts = a.split("."); const bParts = b.split("."); const len = Math.max(aParts.length, bParts.length); const stringToNum = (str) => parseInt(str.match(/\d+/)?.[0] || "0") || 0; for (let i = 0; i < len; i++) { const aPart = stringToNum(aParts[i]) || 0; const bPart = stringToNum(bParts[i]) || 0; if (aPart > bPart) return 1; if (aPart < bPart) return -1; } return 0; } function checkNodeVersion() { const currentNodeVersion = process.version; if (compareVersion(currentNodeVersion, MIN_NODE_VERSION) < 0) return `You are using Node ${currentNodeVersion}, but GPT-Runner requires Node ${MIN_NODE_VERSION}. Please upgrade your Node version in https://nodejs.org/en/download`; } function canUseNodeFetchWithoutCliFlag() { const currentNodeVersion = process.version; return compareVersion(currentNodeVersion, "18.0.0") > 0; } function getRunServerEnv() { if (!canUseNodeFetchWithoutCliFlag()) { return { NODE_OPTIONS: "--experimental-fetch", NODE_NO_WARNINGS: "1" }; } return {}; } class FileUtils { static async readFile(params) { const { filePath, valid = true } = params; if (typeof filePath !== "string") return ""; if (valid && !PathUtils.isFile(filePath)) return ""; if (!filePath) return ""; return promises.readFile(filePath, { encoding: "utf8" }); } static async writeFile(params) { const { filePath, content, overwrite = true, valid = true } = params; if (valid) { if (!PathUtils.isAccessible(filePath, "W")) return; if (!PathUtils.isFile(filePath)) return; } const dir = PathUtils.getDirPath(filePath); if (!PathUtils.isExit(dir)) await promises.mkdir(dir, { recursive: true }); if (overwrite) await promises.writeFile(filePath, content, { encoding: "utf8" }); else await promises.appendFile(filePath, content, { encoding: "utf8" }); } static async deletePath(fullPath) { if (!PathUtils.isAccessible(fullPath, "W")) await promises.rm(fullPath, { recursive: true }); } static async ensurePath(params) { const { filePath } = params; if (!PathUtils.isAccessible(filePath, "W")) await promises.mkdir(filePath, { recursive: true }); } static async movePath(params) { const { oldPath, newPath } = params; if (PathUtils.isAccessible(oldPath, "W")) await promises.rename(oldPath, newPath); } static async travelFiles(params) { const { isValidPath, callback } = params; const filePath = PathUtils.resolve(params.filePath); if (!PathUtils.isAccessible(filePath, "R")) return; const promises$1 = []; if (PathUtils.isDirectory(filePath)) { const entries = await promises.readdir(filePath); for (const entry of entries) { const fullPath = PathUtils.join(filePath, entry); if (!PathUtils.isAccessible(filePath, "R")) continue; const isValid = await isValidPath(fullPath); if (isValid) promises$1.push(FileUtils.travelFiles({ filePath: fullPath, isValidPath, callback })); } } else { const result = callback(filePath); if (result instanceof Promise) promises$1.push(result); } await Promise.allSettled(promises$1); } static async travelFilesByFilterPattern(params) { const { filePath, isValidPath, callback, exts = [], includes = null, excludes = null } = params; await FileUtils.travelFiles({ filePath, isValidPath: async (filePath2) => { if (exts.length > 0 && PathUtils.isFile(filePath2) && !PathUtils.includeExt(filePath2, exts)) return false; if (!createFilterByPattern(includes)(filePath2)) return false; if (createFilterByPattern(excludes)(filePath2)) return false; if (!isValidPath(filePath2)) return false; return true; }, callback }); } } async function launchEditor(params) { return new Promise((resolve, reject) => { const { path, lineNum, columnNum = 0, editorName, onError } = params; const finalPath = lineNum && columnNum ? `${path}:${lineNum}:${columnNum}` : path; launch(finalPath, editorName, (error) => { if (error) { onError?.(error); reject(error); } }); resolve(); }); } async function launchEditorByPathAndContent(params) { const { path, matchContent, editorName, onError } = params; const content = await FileUtils.readFile({ filePath: path }); let lineNum = 0; let columnNum = 0; if (matchContent) { const matchContentStartIndex = content.indexOf(matchContent); if (matchContentStartIndex !== -1) { const beforeMatchContent = content.slice(0, matchContentStartIndex); lineNum = beforeMatchContent.split("\n").length; columnNum = matchContentStartIndex - beforeMatchContent.lastIndexOf("\n"); } } await launchEditor({ path, lineNum, columnNum, editorName, onError }); return { lineNum, columnNum }; } async function getStorage(storageName) { const cacheFolder = await getGlobalCacheDir("gpt-runner-server"); const storage = await kvsLocalStorage({ name: storageName, storeFilePath: cacheFolder, version: 1 }); return { cacheDir: cacheFolder, storage }; } function openInBrowser(props) { const { url } = props; try { open(url); } catch (error) { throw new Error(`Server is started at ${url} but failed to open browser. ${error}`); } } async function getPort(props) { const { defaultPort, autoFreePort, excludePorts } = props; if (defaultPort) { if (!autoFreePort) return defaultPort; const canUseDefaultPort = await fp.isFreePort(defaultPort); if (canUseDefaultPort) return defaultPort; } const freePorts = await fp.findFreePorts(1, { startPort: 3001, endPort: 9999, isFree: async (port) => { if (excludePorts?.includes(port)) return false; return fp.isFreePort(port); } }); return freePorts[0]; } function getLocalHostname() { return ip.address("public", "ipv4"); } function addNodejsPolyfill() { if (!canUseNodeFetchWithoutCliFlag()) { console.log("GPT Runner: add polyfill for fetch", process.version); globalThis.fetch = fetch; globalThis.Headers = Headers; globalThis.Request = Request; globalThis.Response = Response; } } async function getDefaultProxyUrl() { let proxyUrl = ""; try { const { storage } = await getStorage(ServerStorageName.SecretsConfig); const proxySecret = await storage.get(SecretStorageKey.Proxy); proxyUrl = proxySecret?.proxyUrl ?? ""; } catch (error) { console.error("getDefaultProxyUrl error", error); } if (proxyUrl) return proxyUrl; ["HTTP_PROXY", "HTTPS_PROXY", "ALL_PROXY"].forEach((key) => { if (proxyUrl) return; const upperKey = key.toUpperCase(); const lowerKey = key.toLowerCase(); const upperKeyValue = process.env[upperKey] && process.env[upperKey] !== "undefined" ? process.env[upperKey] || "" : ""; const lowerKeyValue = process.env[lowerKey] && process.env[lowerKey] !== "undefined" ? process.env[lowerKey] || "" : ""; return proxyUrl = upperKeyValue || lowerKeyValue || ""; }); return proxyUrl; } function sendSuccessResponse(res, options) { return res.status(options.status || 200).json(buildSuccessResponse(options)); } function sendFailResponse(res, options) { return res.status(options.status || 400).json(buildFailResponse(options)); } function verifyParamsByZod(params, schema) { verifyZod(schema, params); } export { FileUtils, PathUtils, Tunnel, addNodejsPolyfill, canUseNodeFetchWithoutCliFlag, checkNodeVersion, compareVersion, getDefaultProxyUrl, getGlobalCacheDir, getLocalHostname, getPort, getRunServerEnv, getStorage, launchEditor, launchEditorByPathAndContent, openInBrowser, sendFailResponse, sendSuccessResponse, verifyParamsByZod };