785 lines
20 KiB
JavaScript
785 lines
20 KiB
JavaScript
import fs from 'node:fs'
|
|
import os from 'node:os'
|
|
import path from 'node:path'
|
|
import { spawn } from 'node:child_process'
|
|
|
|
import dotenv from 'dotenv'
|
|
import execa from 'execa'
|
|
import semver from 'semver'
|
|
import kill from 'tree-kill'
|
|
|
|
import { LogHelper } from '@/helpers/log-helper'
|
|
import { RuntimeHelper } from '@/helpers/runtime-helper'
|
|
import { SystemHelper } from '@/helpers/system-helper'
|
|
import { shouldIgnoreTCPServerError } from '@/utilities'
|
|
import {
|
|
MINIMUM_REQUIRED_RAM,
|
|
LEON_VERSION,
|
|
INSTANCE_ID,
|
|
NODE_VERSION,
|
|
PNPM_VERSION,
|
|
PYTHON_VERSION,
|
|
UV_VERSION,
|
|
NODE_RUNTIME_BIN_PATH,
|
|
PNPM_RUNTIME_BIN_PATH,
|
|
PYTHON_RUNTIME_BIN_PATH,
|
|
UV_RUNTIME_BIN_PATH,
|
|
NODEJS_BRIDGE_ENTRY_PATH,
|
|
NODEJS_BRIDGE_VERSION,
|
|
PYTHON_BRIDGE_ENTRY_PATH,
|
|
PYTHON_BRIDGE_RUNTIME_BIN_PATH,
|
|
PYTHON_BRIDGE_VERSION,
|
|
PYTHON_TCP_SERVER_ENTRY_PATH,
|
|
PYTHON_TCP_SERVER_RUNTIME_BIN_PATH,
|
|
PYTHON_TCP_SERVER_VERSION,
|
|
PYTHON_TCP_SERVER_SETTINGS,
|
|
PYTHON_TCP_SERVER_TTS_MODEL_DIR_PATH,
|
|
PYTHON_TCP_SERVER_TTS_MODEL_PATH,
|
|
PYTHON_TCP_SERVER_TTS_BERT_BASE_DIR_PATH,
|
|
PYTHON_TCP_SERVER_ASR_MODEL_DIR_PATH,
|
|
AUDIO_MODELS_PATH,
|
|
PROFILE_DOT_ENV_PATH,
|
|
TSX_CLI_PATH,
|
|
LANG,
|
|
HAS_ASR,
|
|
HAS_TTS,
|
|
HAS_WAKE_WORD,
|
|
SHOULD_START_PYTHON_TCP_SERVER,
|
|
LEON_ROUTING_MODE,
|
|
WORKFLOW_LLM_TARGET,
|
|
AGENT_LLM_TARGET,
|
|
NATIVE_SKILLS_PATH,
|
|
PYTORCH_TORCH_PATH,
|
|
NVIDIA_CUBLAS_PATH,
|
|
NVIDIA_CUDNN_PATH,
|
|
NVIDIA_CUSPARSE_PATH,
|
|
NVIDIA_CUSPARSE_FULL_PATH,
|
|
NVIDIA_NCCL_PATH,
|
|
NVIDIA_NVJITLINK_PATH,
|
|
NVIDIA_NVSHMEM_PATH
|
|
} from '@/constants'
|
|
|
|
dotenv.config({ path: PROFILE_DOT_ENV_PATH })
|
|
|
|
const CHECK_TMP_DIR_PATH = path.join(process.cwd(), 'scripts', 'tmp')
|
|
const PYTHON_TCP_SERVER_TTS_MODEL_CONFIG_PATH = path.join(
|
|
PYTHON_TCP_SERVER_TTS_MODEL_DIR_PATH,
|
|
'config.json'
|
|
)
|
|
const PYTHON_TCP_SERVER_WAKE_WORD_MODEL_DIR_PATH = path.join(
|
|
AUDIO_MODELS_PATH,
|
|
'wake_word'
|
|
)
|
|
const PYTHON_TCP_SERVER_WAKE_WORD_MODEL_PATH = path.join(
|
|
PYTHON_TCP_SERVER_WAKE_WORD_MODEL_DIR_PATH,
|
|
PYTHON_TCP_SERVER_SETTINGS.wake_word.model_file_name
|
|
)
|
|
const PYTHON_TCP_SERVER_WAKE_WORD_MELSPEC_PATH = path.join(
|
|
PYTHON_TCP_SERVER_WAKE_WORD_MODEL_DIR_PATH,
|
|
'melspectrogram.onnx'
|
|
)
|
|
const PYTHON_TCP_SERVER_WAKE_WORD_EMBEDDING_PATH = path.join(
|
|
PYTHON_TCP_SERVER_WAKE_WORD_MODEL_DIR_PATH,
|
|
'embedding.onnx'
|
|
)
|
|
|
|
function createCheckResult(title, severity = 'error') {
|
|
return {
|
|
title,
|
|
severity,
|
|
ok: true,
|
|
details: []
|
|
}
|
|
}
|
|
|
|
function addFailure(result, detail) {
|
|
result.ok = false
|
|
result.details.push(detail)
|
|
}
|
|
|
|
function addDetail(result, detail) {
|
|
result.details.push(detail)
|
|
}
|
|
|
|
function getFormattedCheckStatus(check) {
|
|
if (check.ok) {
|
|
return 'ok'
|
|
}
|
|
|
|
return check.severity === 'warning' ? 'warning' : 'error'
|
|
}
|
|
|
|
function extractFirstVersion(rawOutput) {
|
|
const match = rawOutput.match(/\d+\.\d+\.\d+/)
|
|
return match ? match[0] : null
|
|
}
|
|
|
|
async function getCommandVersion(executablePath, args = []) {
|
|
const result = await execa(executablePath, args)
|
|
|
|
return {
|
|
raw: result.stdout.trim(),
|
|
version: extractFirstVersion(result.stdout.trim())
|
|
}
|
|
}
|
|
|
|
async function writeCheckIntentObject(fileName, intentObject) {
|
|
await fs.promises.mkdir(CHECK_TMP_DIR_PATH, { recursive: true })
|
|
|
|
const filePath = path.join(CHECK_TMP_DIR_PATH, fileName)
|
|
await fs.promises.writeFile(filePath, JSON.stringify(intentObject, null, 2))
|
|
|
|
return filePath
|
|
}
|
|
|
|
function buildExtraContext(lang) {
|
|
const now = new Date()
|
|
|
|
return {
|
|
lang,
|
|
date: now.toISOString().slice(0, 10),
|
|
time: now.toTimeString().slice(0, 8),
|
|
timestamp: now.getTime(),
|
|
date_time: now.toISOString(),
|
|
week_day: now.toLocaleDateString('en-US', { weekday: 'long' })
|
|
}
|
|
}
|
|
|
|
async function createNodejsBridgeIntentObject() {
|
|
const skillConfigPath = path.join(
|
|
NATIVE_SKILLS_PATH,
|
|
'date_time_skill',
|
|
'skill.json'
|
|
)
|
|
const skillConfig = JSON.parse(await fs.promises.readFile(skillConfigPath, 'utf8'))
|
|
|
|
return writeCheckIntentObject('check-nodejs-bridge-intent-object.json', {
|
|
id: 'check-nodejs-bridge',
|
|
lang: 'en',
|
|
context_name: 'date_time',
|
|
skill_name: 'date_time_skill',
|
|
action_name: 'current_time',
|
|
skill_config: skillConfig,
|
|
skill_config_path: skillConfigPath,
|
|
utterance: 'What time is it?',
|
|
entities: [],
|
|
action_arguments: {},
|
|
sentiment: {
|
|
vote: 'neutral',
|
|
score: 0
|
|
},
|
|
context: {
|
|
utterances: ['What time is it?'],
|
|
action_arguments: [{}],
|
|
entities: [],
|
|
sentiments: [{ vote: 'neutral', score: 0 }],
|
|
data: {}
|
|
},
|
|
extra_context: buildExtraContext('en')
|
|
})
|
|
}
|
|
|
|
async function createPythonBridgeIntentObject() {
|
|
const skillConfigPath = path.join(
|
|
NATIVE_SKILLS_PATH,
|
|
'color_skill',
|
|
'skill.json'
|
|
)
|
|
const skillConfig = JSON.parse(await fs.promises.readFile(skillConfigPath, 'utf8'))
|
|
|
|
return writeCheckIntentObject('check-python-bridge-intent-object.json', {
|
|
id: 'check-python-bridge',
|
|
lang: 'en',
|
|
context_name: 'color',
|
|
skill_name: 'color_skill',
|
|
action_name: 'tell_hexadecimal_color',
|
|
skill_config: skillConfig,
|
|
skill_config_path: skillConfigPath,
|
|
utterance: 'What is the hexadecimal color for red?',
|
|
entities: [],
|
|
action_arguments: {
|
|
color_name: 'red'
|
|
},
|
|
sentiment: {
|
|
vote: 'neutral',
|
|
score: 0
|
|
},
|
|
context: {
|
|
utterances: ['What is the hexadecimal color for red?'],
|
|
action_arguments: [{ color_name: 'red' }],
|
|
entities: [],
|
|
sentiments: [{ vote: 'neutral', score: 0 }],
|
|
data: {}
|
|
},
|
|
extra_context: buildExtraContext('en')
|
|
})
|
|
}
|
|
|
|
async function runNodejsBridgeCheck(intentObjectPath) {
|
|
return execa(NODE_RUNTIME_BIN_PATH, [
|
|
TSX_CLI_PATH,
|
|
NODEJS_BRIDGE_ENTRY_PATH,
|
|
'--runtime',
|
|
'skill',
|
|
intentObjectPath
|
|
])
|
|
}
|
|
|
|
async function runPythonBridgeCheck(intentObjectPath) {
|
|
return execa(PYTHON_BRIDGE_RUNTIME_BIN_PATH, [
|
|
PYTHON_BRIDGE_ENTRY_PATH,
|
|
intentObjectPath
|
|
])
|
|
}
|
|
|
|
function buildTCPServerEnv() {
|
|
const env = { ...process.env }
|
|
|
|
if (SystemHelper.isLinux()) {
|
|
const torchLibPath = `${PYTORCH_TORCH_PATH}/lib`
|
|
const nvidiaLibPaths = [
|
|
`${NVIDIA_CUBLAS_PATH}/lib`,
|
|
`${NVIDIA_CUDNN_PATH}/lib`,
|
|
`${NVIDIA_CUSPARSE_PATH}/lib`,
|
|
`${NVIDIA_CUSPARSE_FULL_PATH}/lib`,
|
|
`${NVIDIA_NCCL_PATH}/lib`,
|
|
`${NVIDIA_NVSHMEM_PATH}/lib`,
|
|
`${NVIDIA_NVJITLINK_PATH}/lib`
|
|
]
|
|
|
|
env['LD_LIBRARY_PATH'] = [torchLibPath, ...nvidiaLibPaths, env['LD_LIBRARY_PATH']]
|
|
.filter(Boolean)
|
|
.join(':')
|
|
}
|
|
|
|
return env
|
|
}
|
|
|
|
function startTCPServerCheck() {
|
|
const args = [PYTHON_TCP_SERVER_ENTRY_PATH, LANG || 'en']
|
|
const env = buildTCPServerEnv()
|
|
const commandString = RuntimeHelper.buildShellCommand(
|
|
PYTHON_TCP_SERVER_RUNTIME_BIN_PATH,
|
|
args
|
|
)
|
|
|
|
return new Promise((resolve) => {
|
|
const child = spawn(PYTHON_TCP_SERVER_RUNTIME_BIN_PATH, args, {
|
|
env,
|
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
windowsHide: true
|
|
})
|
|
|
|
const timeoutMs = 3 * 60_000
|
|
let output = ''
|
|
let resolved = false
|
|
|
|
const finish = (result) => {
|
|
if (resolved) {
|
|
return
|
|
}
|
|
|
|
resolved = true
|
|
clearTimeout(timeoutId)
|
|
|
|
if (child.pid) {
|
|
kill(child.pid)
|
|
}
|
|
|
|
resolve({
|
|
command: commandString,
|
|
...result
|
|
})
|
|
}
|
|
|
|
const timeoutId = setTimeout(() => {
|
|
finish({
|
|
ok: false,
|
|
output,
|
|
error: `The Python TCP server timed out after ${timeoutMs}ms`
|
|
})
|
|
}, timeoutMs)
|
|
|
|
child.stdout.on('data', (data) => {
|
|
const text = data.toString()
|
|
output += text
|
|
|
|
if (text.toLowerCase().includes('waiting for connection')) {
|
|
finish({
|
|
ok: true,
|
|
output,
|
|
error: null
|
|
})
|
|
}
|
|
})
|
|
|
|
child.stderr.on('data', (data) => {
|
|
const text = data.toString()
|
|
|
|
if (shouldIgnoreTCPServerError(text)) {
|
|
output += text
|
|
return
|
|
}
|
|
|
|
output += text
|
|
finish({
|
|
ok: false,
|
|
output,
|
|
error: text.trim() || 'Unknown TCP server startup error'
|
|
})
|
|
})
|
|
|
|
child.on('error', (error) => {
|
|
finish({
|
|
ok: false,
|
|
output,
|
|
error: String(error)
|
|
})
|
|
})
|
|
|
|
child.on('exit', (code) => {
|
|
if (!resolved) {
|
|
finish({
|
|
ok: false,
|
|
output,
|
|
error: `The Python TCP server exited before becoming ready (code: ${code ?? 'unknown'})`
|
|
})
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
function validateLocalLLMTarget(target, label, result) {
|
|
if (!target.isEnabled) {
|
|
addDetail(result, `${label}: disabled`)
|
|
return
|
|
}
|
|
|
|
if (!target.isLocal) {
|
|
addDetail(result, `${label}: ${target.label}`)
|
|
return
|
|
}
|
|
|
|
if (!target.isResolved) {
|
|
addFailure(
|
|
result,
|
|
`${label}: ${target.resolutionError || 'local model is not configured or installed yet'}`
|
|
)
|
|
return
|
|
}
|
|
|
|
if (!fs.existsSync(target.model)) {
|
|
addFailure(
|
|
result,
|
|
`${label}: local model not found at ${target.model}`
|
|
)
|
|
return
|
|
}
|
|
|
|
addDetail(result, `${label}: ${target.label}`)
|
|
}
|
|
|
|
function checkTCPServerAssets() {
|
|
const result = createCheckResult('Python TCP server assets')
|
|
|
|
if (HAS_TTS) {
|
|
if (!fs.existsSync(PYTHON_TCP_SERVER_TTS_MODEL_CONFIG_PATH)) {
|
|
addFailure(
|
|
result,
|
|
`Missing TTS config at ${PYTHON_TCP_SERVER_TTS_MODEL_CONFIG_PATH}`
|
|
)
|
|
}
|
|
|
|
if (!fs.existsSync(PYTHON_TCP_SERVER_TTS_MODEL_PATH)) {
|
|
addFailure(
|
|
result,
|
|
`Missing TTS model at ${PYTHON_TCP_SERVER_TTS_MODEL_PATH}`
|
|
)
|
|
}
|
|
|
|
if (!fs.existsSync(PYTHON_TCP_SERVER_TTS_BERT_BASE_DIR_PATH)) {
|
|
addFailure(
|
|
result,
|
|
`Missing TTS BERT base directory at ${PYTHON_TCP_SERVER_TTS_BERT_BASE_DIR_PATH}`
|
|
)
|
|
}
|
|
}
|
|
|
|
if (HAS_ASR && !fs.existsSync(PYTHON_TCP_SERVER_ASR_MODEL_DIR_PATH)) {
|
|
addFailure(
|
|
result,
|
|
`Missing ASR model directory at ${PYTHON_TCP_SERVER_ASR_MODEL_DIR_PATH}`
|
|
)
|
|
}
|
|
|
|
if (HAS_WAKE_WORD) {
|
|
if (!fs.existsSync(PYTHON_TCP_SERVER_WAKE_WORD_MODEL_PATH)) {
|
|
addFailure(
|
|
result,
|
|
`Missing wake word model at ${PYTHON_TCP_SERVER_WAKE_WORD_MODEL_PATH}`
|
|
)
|
|
}
|
|
|
|
if (!fs.existsSync(PYTHON_TCP_SERVER_WAKE_WORD_MELSPEC_PATH)) {
|
|
addFailure(
|
|
result,
|
|
`Missing wake word melspectrogram model at ${PYTHON_TCP_SERVER_WAKE_WORD_MELSPEC_PATH}`
|
|
)
|
|
}
|
|
|
|
if (!fs.existsSync(PYTHON_TCP_SERVER_WAKE_WORD_EMBEDDING_PATH)) {
|
|
addFailure(
|
|
result,
|
|
`Missing wake word embedding model at ${PYTHON_TCP_SERVER_WAKE_WORD_EMBEDDING_PATH}`
|
|
)
|
|
}
|
|
}
|
|
|
|
if (result.ok) {
|
|
addDetail(
|
|
result,
|
|
`ASR=${HAS_ASR ? 'enabled' : 'disabled'}, TTS=${HAS_TTS ? 'enabled' : 'disabled'}, wake_word=${HAS_WAKE_WORD ? 'enabled' : 'disabled'}`
|
|
)
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
function printCheckResult(result) {
|
|
const status = getFormattedCheckStatus(result)
|
|
|
|
if (status === 'ok') {
|
|
LogHelper.success(result.title)
|
|
} else if (status === 'warning') {
|
|
LogHelper.warning(result.title)
|
|
} else {
|
|
LogHelper.error(result.title)
|
|
}
|
|
|
|
result.details.forEach((detail) => {
|
|
LogHelper.default(`- ${detail}`)
|
|
})
|
|
}
|
|
|
|
;(async () => {
|
|
const checks = {
|
|
canRun: createCheckResult('Run Leon'),
|
|
hardware: createCheckResult('Hardware'),
|
|
managedNode: createCheckResult('Managed Node.js runtime'),
|
|
managedPNPM: createCheckResult('Managed pnpm runtime'),
|
|
managedPython: createCheckResult('Managed Python runtime'),
|
|
managedUV: createCheckResult('Managed uv runtime'),
|
|
llmTargets: createCheckResult('LLM target configuration'),
|
|
nodeJSBridge: createCheckResult('Node.js bridge smoke test'),
|
|
pythonBridge: createCheckResult('Python bridge smoke test'),
|
|
pythonTCPServerAssets: createCheckResult('Python TCP server assets'),
|
|
pythonTCPServer: createCheckResult('Python TCP server boot')
|
|
}
|
|
|
|
const summary = {
|
|
leonVersion: LEON_VERSION || null,
|
|
instanceID: INSTANCE_ID || null,
|
|
environment: {
|
|
os: SystemHelper.getInformation(),
|
|
release: os.release(),
|
|
cpus: os.cpus().length,
|
|
totalRAMInGB: SystemHelper.getTotalRAM(),
|
|
freeRAMInGB: SystemHelper.getFreeRAM()
|
|
},
|
|
hardware: {},
|
|
runtimes: {},
|
|
llm: {
|
|
routingMode: LEON_ROUTING_MODE,
|
|
workflow: WORKFLOW_LLM_TARGET.label,
|
|
agent: AGENT_LLM_TARGET.label
|
|
},
|
|
nodeJSBridge: {},
|
|
pythonBridge: {},
|
|
pythonTCPServer: {}
|
|
}
|
|
|
|
LogHelper.title('Checking')
|
|
|
|
LogHelper.info('Leon version')
|
|
LogHelper.success(String(LEON_VERSION))
|
|
|
|
LogHelper.info('Environment')
|
|
LogHelper.success(JSON.stringify(summary.environment))
|
|
|
|
try {
|
|
const [
|
|
gpuDeviceNames,
|
|
graphicsComputeAPI,
|
|
totalVRAM,
|
|
freeVRAM,
|
|
usedVRAM,
|
|
canSupportLocalLLM
|
|
] = await Promise.all([
|
|
SystemHelper.getGPUDeviceNames(),
|
|
SystemHelper.getGraphicsComputeAPI(),
|
|
SystemHelper.getTotalVRAM(),
|
|
SystemHelper.getFreeVRAM(),
|
|
SystemHelper.getUsedVRAM(),
|
|
SystemHelper.canSupportLocalLLM()
|
|
])
|
|
|
|
summary.hardware = {
|
|
gpuDeviceNames,
|
|
graphicsComputeAPI,
|
|
totalVRAMInGB: totalVRAM,
|
|
freeVRAMInGB: freeVRAM,
|
|
usedVRAMInGB: usedVRAM,
|
|
canSupportLocalLLM
|
|
}
|
|
|
|
addDetail(
|
|
checks.hardware,
|
|
`GPU: ${gpuDeviceNames.length > 0 ? gpuDeviceNames.join(', ') : 'none'}`
|
|
)
|
|
addDetail(checks.hardware, `Compute API: ${graphicsComputeAPI}`)
|
|
addDetail(
|
|
checks.hardware,
|
|
`VRAM: total=${totalVRAM} GB | free=${freeVRAM} GB | used=${usedVRAM} GB`
|
|
)
|
|
addDetail(
|
|
checks.hardware,
|
|
`Local LLM support: ${canSupportLocalLLM ? 'yes' : 'no'}`
|
|
)
|
|
} catch (error) {
|
|
checks.hardware.severity = 'warning'
|
|
addFailure(
|
|
checks.hardware,
|
|
`Unable to inspect GPU/VRAM information: ${String(error)}`
|
|
)
|
|
}
|
|
|
|
if (Math.round(summary.environment.freeRAMInGB) < MINIMUM_REQUIRED_RAM) {
|
|
addFailure(
|
|
checks.canRun,
|
|
`Free RAM is ${summary.environment.freeRAMInGB} GB but Leon needs at least ${MINIMUM_REQUIRED_RAM} GB`
|
|
)
|
|
} else {
|
|
addDetail(
|
|
checks.canRun,
|
|
`Free RAM: ${summary.environment.freeRAMInGB} GB | Total RAM: ${summary.environment.totalRAMInGB} GB`
|
|
)
|
|
}
|
|
|
|
const runtimeChecks = [
|
|
{
|
|
key: 'managedNode',
|
|
label: 'node',
|
|
executablePath: NODE_RUNTIME_BIN_PATH,
|
|
expectedVersion: NODE_VERSION
|
|
},
|
|
{
|
|
key: 'managedPNPM',
|
|
label: 'pnpm',
|
|
executablePath: PNPM_RUNTIME_BIN_PATH,
|
|
expectedVersion: PNPM_VERSION
|
|
},
|
|
{
|
|
key: 'managedPython',
|
|
label: 'python',
|
|
executablePath: PYTHON_RUNTIME_BIN_PATH,
|
|
expectedVersion: PYTHON_VERSION
|
|
},
|
|
{
|
|
key: 'managedUV',
|
|
label: 'uv',
|
|
executablePath: UV_RUNTIME_BIN_PATH,
|
|
expectedVersion: UV_VERSION
|
|
}
|
|
]
|
|
|
|
for (const runtimeCheck of runtimeChecks) {
|
|
const result = checks[runtimeCheck.key]
|
|
|
|
if (!fs.existsSync(runtimeCheck.executablePath)) {
|
|
addFailure(
|
|
result,
|
|
`Runtime not found at ${runtimeCheck.executablePath}`
|
|
)
|
|
continue
|
|
}
|
|
|
|
try {
|
|
const { raw, version } = await getCommandVersion(
|
|
runtimeCheck.executablePath,
|
|
['--version']
|
|
)
|
|
summary.runtimes[runtimeCheck.label] = {
|
|
path: runtimeCheck.executablePath,
|
|
version: raw
|
|
}
|
|
|
|
addDetail(result, `Path: ${runtimeCheck.executablePath}`)
|
|
addDetail(result, `Version: ${raw}`)
|
|
|
|
const expectedVersion = extractFirstVersion(runtimeCheck.expectedVersion)
|
|
if (
|
|
expectedVersion &&
|
|
version &&
|
|
semver.valid(semver.coerce(version)) &&
|
|
semver.valid(semver.coerce(expectedVersion)) &&
|
|
semver.neq(
|
|
semver.coerce(version),
|
|
semver.coerce(expectedVersion)
|
|
)
|
|
) {
|
|
addFailure(
|
|
result,
|
|
`Expected ${expectedVersion} but got ${version}`
|
|
)
|
|
}
|
|
} catch (error) {
|
|
addFailure(result, String(error))
|
|
}
|
|
}
|
|
|
|
if (!fs.existsSync(TSX_CLI_PATH)) {
|
|
addFailure(
|
|
checks.nodeJSBridge,
|
|
`Missing tsx CLI at ${TSX_CLI_PATH}`
|
|
)
|
|
}
|
|
|
|
validateLocalLLMTarget(WORKFLOW_LLM_TARGET, 'Workflow', checks.llmTargets)
|
|
validateLocalLLMTarget(AGENT_LLM_TARGET, 'Agent', checks.llmTargets)
|
|
|
|
const pythonTCPServerAssetsResult = checkTCPServerAssets()
|
|
checks.pythonTCPServerAssets = pythonTCPServerAssetsResult
|
|
|
|
let nodeIntentObjectPath = null
|
|
let pythonIntentObjectPath = null
|
|
|
|
try {
|
|
nodeIntentObjectPath = await createNodejsBridgeIntentObject()
|
|
|
|
const executionStart = Date.now()
|
|
const result = await runNodejsBridgeCheck(nodeIntentObjectPath)
|
|
const executionTime = Date.now() - executionStart
|
|
const combinedOutput = `${result.stdout}\n${result.stderr}`.trim()
|
|
|
|
summary.nodeJSBridge = {
|
|
version: NODEJS_BRIDGE_VERSION,
|
|
command: result.command,
|
|
executionTimeMs: executionTime,
|
|
output: combinedOutput
|
|
}
|
|
|
|
addDetail(checks.nodeJSBridge, `Version: ${NODEJS_BRIDGE_VERSION}`)
|
|
addDetail(checks.nodeJSBridge, `Execution time: ${executionTime}ms`)
|
|
|
|
if (
|
|
combinedOutput.includes('Error while running') ||
|
|
combinedOutput.trim() === ''
|
|
) {
|
|
addFailure(
|
|
checks.nodeJSBridge,
|
|
combinedOutput || 'The Node.js bridge did not produce any output'
|
|
)
|
|
}
|
|
} catch (error) {
|
|
addFailure(checks.nodeJSBridge, String(error))
|
|
}
|
|
|
|
try {
|
|
pythonIntentObjectPath = await createPythonBridgeIntentObject()
|
|
|
|
const executionStart = Date.now()
|
|
const result = await runPythonBridgeCheck(pythonIntentObjectPath)
|
|
const executionTime = Date.now() - executionStart
|
|
const combinedOutput = `${result.stdout}\n${result.stderr}`.trim()
|
|
|
|
summary.pythonBridge = {
|
|
version: PYTHON_BRIDGE_VERSION,
|
|
command: result.command,
|
|
executionTimeMs: executionTime,
|
|
output: combinedOutput
|
|
}
|
|
|
|
addDetail(checks.pythonBridge, `Version: ${PYTHON_BRIDGE_VERSION}`)
|
|
addDetail(checks.pythonBridge, `Execution time: ${executionTime}ms`)
|
|
|
|
if (
|
|
combinedOutput.includes('Error while running') ||
|
|
combinedOutput.includes('Traceback') ||
|
|
combinedOutput.trim() === ''
|
|
) {
|
|
addFailure(
|
|
checks.pythonBridge,
|
|
combinedOutput || 'The Python bridge did not produce any output'
|
|
)
|
|
}
|
|
} catch (error) {
|
|
addFailure(checks.pythonBridge, String(error))
|
|
}
|
|
|
|
if (SHOULD_START_PYTHON_TCP_SERVER) {
|
|
try {
|
|
const startupStart = Date.now()
|
|
const result = await startTCPServerCheck()
|
|
const startupTime = Date.now() - startupStart
|
|
|
|
summary.pythonTCPServer = {
|
|
version: PYTHON_TCP_SERVER_VERSION,
|
|
command: result.command,
|
|
startupTimeMs: startupTime,
|
|
output: result.output
|
|
}
|
|
|
|
addDetail(checks.pythonTCPServer, `Version: ${PYTHON_TCP_SERVER_VERSION}`)
|
|
addDetail(checks.pythonTCPServer, `Startup time: ${startupTime}ms`)
|
|
|
|
if (!result.ok) {
|
|
addFailure(
|
|
checks.pythonTCPServer,
|
|
result.error || 'The Python TCP server did not become ready'
|
|
)
|
|
}
|
|
} catch (error) {
|
|
addFailure(checks.pythonTCPServer, String(error))
|
|
}
|
|
} else {
|
|
checks.pythonTCPServer.severity = 'warning'
|
|
addDetail(
|
|
checks.pythonTCPServer,
|
|
'Skipped because neither ASR nor TTS is enabled'
|
|
)
|
|
}
|
|
|
|
for (const check of Object.values(checks)) {
|
|
printCheckResult(check)
|
|
}
|
|
|
|
const criticalFailures = Object.values(checks).some(
|
|
(check) => check.severity === 'error' && !check.ok
|
|
)
|
|
|
|
LogHelper.title('Summary')
|
|
|
|
if (criticalFailures || !checks.canRun.ok) {
|
|
LogHelper.error('Please fix the errors above')
|
|
} else {
|
|
LogHelper.success('Hooray! Leon can run correctly')
|
|
}
|
|
|
|
try {
|
|
if (nodeIntentObjectPath) {
|
|
await fs.promises.rm(nodeIntentObjectPath, { force: true })
|
|
}
|
|
if (pythonIntentObjectPath) {
|
|
await fs.promises.rm(pythonIntentObjectPath, { force: true })
|
|
}
|
|
} catch {
|
|
// Ignore cleanup failures for temporary check fixtures.
|
|
}
|
|
|
|
process.exit(criticalFailures || !checks.canRun.ok ? 1 : 0)
|
|
})().catch((error) => {
|
|
LogHelper.title('Checking')
|
|
LogHelper.error(`Unexpected error: ${error}`)
|
|
process.exit(1)
|
|
})
|