@vectorx/cloud-toolkit
Version:
VectorX Cloud Toolkit
308 lines (307 loc) • 13.9 kB
JavaScript
;
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;
}
}