UNPKG

@vectorx/cloud-toolkit

Version:

VectorX Cloud Toolkit

308 lines (307 loc) 13.9 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.startRcbFramework = startRcbFramework; exports.startDev = startDev; exports.build = build; exports.createNew = createNew; exports.cleanup = cleanup; const child_process_1 = require("child_process"); const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); const agent_simulator_1 = require("@vectorx/agent-simulator"); const archiver_1 = __importDefault(require("archiver")); const semver_1 = __importDefault(require("semver")); const container_1 = require("./container"); const env_helper_1 = require("./utils/env-helper"); const port_finder_1 = require("./utils/port-finder"); const project_validator_1 = require("./utils/project-validator"); let rcbProcess = null; function validatePort(port, name = "端口") { if (isNaN(port) || port < 1 || port > 65535) { throw new Error(`无效的${name}号: ${port}${name}号必须在 1-65535 之间`); } } function startRcbFramework(workDir_1, port_1, watch_1) { return __awaiter(this, arguments, void 0, function* (workDir, port, watch, nodePath = "node", options) { return new Promise((resolve, reject) => { const args = [ ...(watch ? ["--watch"] : []), "--directory", workDir, "--port", port.toString(), "--enable-cors", "--allowed-origins", "localhost,127.0.0.1", "--config", "agent-cloudbase-functions.json", ...((options === null || options === void 0 ? void 0 : options.enableRedLangfuse) ? ["--enableRedLangfuse"] : []), ]; try { const packagePath = require.resolve("@vectorx/functions-framework/package.json"); const packageDir = path_1.default.dirname(packagePath); const rcbFfPath = path_1.default.join(packageDir, "bin/rcb-ff.js"); console.log("开始启动 rcb-ff 服务..."); if (env_helper_1.isIDE) nodePath = nodePath || "node"; rcbProcess = (0, child_process_1.spawn)(nodePath, [rcbFfPath, ...args], { stdio: ["pipe", "pipe", "pipe", "ipc"], cwd: workDir, }); rcbProcess.stdout.on("data", (data) => { const output = data.toString(); console.log(output.trim()); }); rcbProcess.stderr.on("data", (data) => { const error = data.toString(); console.error(error.trim()); }); rcbProcess.on("message", (message) => { if ((message.type === "message" && message.data.type === "start-up") || (message.type === "start-up" && message.status === "success")) { console.log("启动 rcb-ff 服务完成"); resolve(rcbProcess); } }); rcbProcess.on("error", (error) => { console.error(`启动 rcb-ff 失败: ${error}`); reject(error); }); rcbProcess.on("unhandledRejection", (error) => { console.error(`unhandledRejection 错误: ${error}`); }); rcbProcess.on("exit", (code) => { if (code !== 0) { reject(new Error(`rcb-ff 进程退出,退出码: ${code}`)); } }); } catch (error) { reject(new Error(`找不到 @vectorx/functions-framework 包: ${error}`)); } }); }); } function startDev(options) { return __awaiter(this, void 0, void 0, function* () { const { workDir = process.cwd(), port: specifiedPort, simulatorPort: specifiedSimulatorPort, watch = false, enableRedLangfuse = false, } = options; if (!fs_1.default.existsSync(workDir)) { throw new Error(`目录不存在: ${workDir}`); } let port; if (specifiedPort) { validatePort(specifiedPort, "Agent Server 端口"); const isAvailable = yield (0, port_finder_1.isPortAvailable)(specifiedPort); if (!isAvailable) { throw new Error(`端口 ${specifiedPort} 已被占用,请选择其他端口或不指定端口以自动分配`); } port = specifiedPort; } else { port = yield (0, port_finder_1.findAvailablePort)(3000); } let simulatorPort; if (specifiedSimulatorPort) { validatePort(specifiedSimulatorPort, "模拟器端口"); if (specifiedSimulatorPort === port) { throw new Error(`模拟器端口 ${specifiedSimulatorPort} 与 Agent Server 端口冲突,请选择不同的端口`); } const isAvailable = yield (0, port_finder_1.isPortAvailable)(specifiedSimulatorPort); if (!isAvailable) { throw new Error(`模拟器端口 ${specifiedSimulatorPort} 已被占用,请选择其他端口或不指定端口以自动分配`); } simulatorPort = specifiedSimulatorPort; } else { simulatorPort = yield (0, port_finder_1.findAvailablePort)(port + 1); } yield startRcbFramework(workDir, port, watch, undefined, { enableRedLangfuse }); const simulator = new agent_simulator_1.AgentSimulator({ port: simulatorPort, agentServerUrl: `http://localhost:${port}/v1/aiagent/agents/demo-agent`, open: false, }); yield simulator.start(); return { port, simulatorPort, simulator, rcbProcess, }; }); } function build(options) { return __awaiter(this, void 0, void 0, function* () { const { workDir = process.cwd(), outputDir = path_1.default.join(workDir, "dist"), filename = "agent.zip", upload = false, uploadInfo, } = options; if (!fs_1.default.existsSync(workDir)) { throw new Error(`目录不存在: ${workDir}`); } const validator = new project_validator_1.ProjectValidator(workDir, uploadInfo); if (validator) { const validationResult = yield validator.validate(); if (!validationResult.valid) { throw new Error(`项目验证失败:\n${validationResult.errors.join("\n")}`); } } if (!fs_1.default.existsSync(outputDir)) { fs_1.default.mkdirSync(outputDir, { recursive: true }); } const zipFilename = filename.endsWith(".zip") ? filename : `${filename}.zip`; const outputPath = path_1.default.join(outputDir, zipFilename); yield new Promise((resolve, reject) => { const output = fs_1.default.createWriteStream(outputPath); const archive = (0, archiver_1.default)("zip", { zlib: { level: 9 }, }); output.on("close", () => { resolve(); }); archive.on("error", (err) => { reject(err); }); archive.pipe(output); const filesToAdd = []; const collectFiles = (dir, basePath = "") => { const files = fs_1.default.readdirSync(dir); for (const file of files) { const fullPath = path_1.default.join(dir, file); const relativePath = path_1.default.join(basePath, file); const stat = fs_1.default.statSync(fullPath); const excludePatterns = [/^\.git/, /^dist/, /\.zip$/, /\.DS_Store$/]; if (excludePatterns.some((pattern) => pattern.test(relativePath))) { continue; } if (stat.isDirectory()) { collectFiles(fullPath, relativePath); } else { const isExecutable = relativePath.startsWith("bin/") || relativePath.includes("/bin/") || (() => { try { const content = fs_1.default.readFileSync(fullPath, "utf8"); return content.startsWith("#!/"); } catch (_a) { return false; } })(); filesToAdd.push({ fullPath, relativePath, isExecutable }); } } }; collectFiles(workDir); for (const { fullPath, relativePath, isExecutable } of filesToAdd) { if (isExecutable) { archive.file(fullPath, { name: relativePath, mode: 0o755 }); } else { archive.file(fullPath, { name: relativePath }); } } archive.finalize(); }); const size = fs_1.default.statSync(outputPath).size; const uploadService = container_1.container.get(container_1.SERVICE_IDENTIFIERS.UploadService); if (upload && uploadService) { try { const projectConfigPath = path_1.default.join(workDir, "project.config.json"); if (!fs_1.default.existsSync(projectConfigPath)) { throw new Error("找不到项目配置文件: project.config.json"); } const projectInfo = JSON.parse(fs_1.default.readFileSync(projectConfigPath, "utf-8")); if (!projectInfo.agentId) { throw new Error("项目配置文件缺少必要字段 (agentId)"); } let version, desc; if (!env_helper_1.isIDE) { if (!projectInfo.version || !projectInfo.desc) { throw new Error("项目配置文件缺少必要字段 (agentId, version, desc)"); } if (!semver_1.default.valid(projectInfo.version)) { throw new Error("项目配置文件中 version 字段不符合 semver 规范"); } version = projectInfo.version; desc = projectInfo.desc; } else if (uploadInfo && env_helper_1.isIDE) { version = uploadInfo.version; desc = uploadInfo.desc; } const result = yield uploadService.deploy({ targetPath: outputPath, agentId: projectInfo.agentId, version, desc, }); if (!result.success || result.code !== 0) { throw new Error(result.message || "部署失败"); } } catch (error) { throw new Error(`部署失败: ${error.message}`); } } return { outputPath, size, }; }); } function createNew(options) { return __awaiter(this, void 0, void 0, function* () { const { projectName, targetDir = process.cwd() } = options; if (!projectName) { throw new Error("项目名称不能为空"); } const projectDir = path_1.default.join(targetDir, projectName); if (fs_1.default.existsSync(projectDir)) { throw new Error(`项目目录已存在: ${projectDir}`); } const templateDir = path_1.default.join(__dirname, "../templates/chatbox-agent"); if (!fs_1.default.existsSync(templateDir)) { throw new Error(`模板目录不存在: ${templateDir}`); } yield copyTemplate(templateDir, projectDir); return { projectDir, }; }); } function copyTemplate(sourceDir, targetDir) { return __awaiter(this, void 0, void 0, function* () { if (!fs_1.default.existsSync(targetDir)) { fs_1.default.mkdirSync(targetDir, { recursive: true }); } const files = fs_1.default.readdirSync(sourceDir); for (const file of files) { const sourcePath = path_1.default.join(sourceDir, file); const targetPath = path_1.default.join(targetDir, file); const stat = fs_1.default.statSync(sourcePath); if (stat.isDirectory()) { yield copyTemplate(sourcePath, targetPath); } else { fs_1.default.copyFileSync(sourcePath, targetPath); } } }); } function cleanup() { if (rcbProcess) { rcbProcess.kill("SIGTERM"); rcbProcess = null; } }