Files
boss/src/lib/execution/backends/hermes-config.ts

213 lines
5.4 KiB
TypeScript

import { constants } from "node:fs";
import { access } from "node:fs/promises";
import path from "node:path";
export interface HermesBackendConfig {
enabled: boolean;
command: string;
args: string[];
cwd?: string;
timeoutMs: number;
defaultModel?: string;
toolsets: string[];
skills: string[];
sourceTag: string;
}
export type HermesBackendAvailabilityStatus = "disabled" | "misconfigured" | "ready";
export interface HermesBackendAvailability {
status: HermesBackendAvailabilityStatus;
selectable: boolean;
configured: boolean;
reason:
| "disabled"
| "command_not_set"
| "command_not_found"
| "workdir_not_found"
| "script_not_found"
| "ready";
reasonLabel: string;
command?: string;
cwd?: string;
}
function parseBoolean(value: string | undefined) {
return value?.trim().toLowerCase() === "true";
}
function parseArgs(value: string | undefined) {
return String(value || "")
.trim()
.split(/\s+/)
.filter(Boolean);
}
function parseCsv(value: string | undefined) {
return String(value || "")
.split(",")
.map((item) => item.trim())
.filter(Boolean);
}
function parseTimeoutMs(value: string | undefined) {
const parsed = Number.parseInt(value || "", 10);
return Number.isFinite(parsed) && parsed > 0 ? parsed : 120000;
}
export function getHermesBackendConfig(): HermesBackendConfig {
return {
enabled: parseBoolean(process.env.BOSS_HERMES_ENABLED),
command: process.env.BOSS_HERMES_COMMAND?.trim() || "hermes",
args: parseArgs(process.env.BOSS_HERMES_ARGS),
cwd: process.env.BOSS_HERMES_WORKDIR?.trim() || undefined,
timeoutMs: parseTimeoutMs(process.env.BOSS_HERMES_TIMEOUT_MS),
defaultModel: process.env.BOSS_HERMES_DEFAULT_MODEL?.trim() || undefined,
toolsets: parseCsv(process.env.BOSS_HERMES_TOOLSETS),
skills: parseCsv(process.env.BOSS_HERMES_SKILLS),
sourceTag: process.env.BOSS_HERMES_SOURCE_TAG?.trim() || "tool",
};
}
export function isHermesBackendConfigured(config: HermesBackendConfig) {
return config.enabled && Boolean(config.command);
}
function commandLooksLikePath(command: string) {
return command.includes("/") || command.includes("\\");
}
async function fileExists(filePath: string, mode = constants.F_OK) {
try {
await access(filePath, mode);
return true;
} catch {
return false;
}
}
function resolveScriptCandidate(config: HermesBackendConfig) {
if (!config.command || config.args.length === 0) {
return null;
}
const commandName = path.basename(config.command).toLowerCase();
const scriptRuntimes = new Set([
"node",
"node.exe",
"tsx",
"tsx.cmd",
"bun",
"bun.exe",
"deno",
"deno.exe",
"python",
"python.exe",
"python3",
"python3.exe",
]);
if (!scriptRuntimes.has(commandName)) {
return null;
}
const candidate = config.args[0];
if (!candidate || candidate.startsWith("-")) {
return null;
}
return path.isAbsolute(candidate)
? candidate
: path.resolve(config.cwd?.trim() || process.cwd(), candidate);
}
async function isCommandReachable(command: string) {
if (commandLooksLikePath(command)) {
return fileExists(path.resolve(command), constants.X_OK);
}
const searchPaths = (process.env.PATH || "")
.split(path.delimiter)
.map((entry) => entry.trim())
.filter(Boolean);
for (const entry of searchPaths) {
const candidate = path.join(entry, command);
if (await fileExists(candidate, constants.X_OK)) {
return true;
}
}
return false;
}
export async function getHermesBackendAvailability(
config: HermesBackendConfig = getHermesBackendConfig(),
): Promise<HermesBackendAvailability> {
const base = {
command: config.command,
cwd: config.cwd,
configured: isHermesBackendConfigured(config),
};
if (!config.enabled) {
return {
...base,
status: "disabled",
selectable: false,
reason: "disabled",
reasonLabel: "Hermes Runtime 当前未启用。",
};
}
if (!config.command) {
return {
...base,
status: "misconfigured",
selectable: false,
reason: "command_not_set",
reasonLabel: "Hermes Runtime 缺少启动命令。",
};
}
if (!(await isCommandReachable(config.command))) {
return {
...base,
status: "misconfigured",
selectable: false,
reason: "command_not_found",
reasonLabel: "未检测到可执行的 Hermes 启动命令。",
};
}
if (config.cwd && !(await fileExists(config.cwd, constants.F_OK))) {
return {
...base,
status: "misconfigured",
selectable: false,
reason: "workdir_not_found",
reasonLabel: "Hermes Runtime 工作目录不存在。",
};
}
const scriptCandidate = resolveScriptCandidate(config);
if (scriptCandidate && !(await fileExists(scriptCandidate, constants.F_OK))) {
return {
...base,
status: "misconfigured",
selectable: false,
reason: "script_not_found",
reasonLabel: "未检测到有效的 Hermes 启动脚本,将自动回退到默认后端。",
};
}
return {
...base,
status: "ready",
selectable: true,
reason: "ready",
reasonLabel: "Hermes Runtime 可用。",
};
}
export const getHermesBackendConfigForTesting = getHermesBackendConfig;
export const isHermesBackendConfiguredForTesting = isHermesBackendConfigured;
export const getHermesBackendAvailabilityForTesting = getHermesBackendAvailability;