mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-07 22:24:06 -05:00
fix(node): use node subprocess explicitly (#2391)
* fix(node): use node subprocess explicitly * add explicit documentation * fixed build
This commit is contained in:
@@ -130,6 +130,7 @@ When running with Docker, use `host.docker.internal` if vLLM is on your host mac
|
||||
|
||||
**Requirements:**
|
||||
- [Bun](https://bun.sh/) runtime
|
||||
- [Node.js](https://nodejs.org/) v20+ (required for sandboxed code execution)
|
||||
- PostgreSQL 12+ with [pgvector extension](https://github.com/pgvector/pgvector) (required for AI embeddings)
|
||||
|
||||
**Note:** Sim uses vector embeddings for AI features like knowledge bases and semantic search, which requires the `pgvector` PostgreSQL extension.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type ChildProcess, spawn } from 'node:child_process'
|
||||
import { type ChildProcess, execSync } from 'node:child_process'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
@@ -7,6 +7,19 @@ import { createLogger } from '@/lib/logs/console/logger'
|
||||
|
||||
const logger = createLogger('IsolatedVMExecution')
|
||||
|
||||
let nodeAvailable: boolean | null = null
|
||||
|
||||
function checkNodeAvailable(): boolean {
|
||||
if (nodeAvailable !== null) return nodeAvailable
|
||||
try {
|
||||
execSync('node --version', { stdio: 'ignore' })
|
||||
nodeAvailable = true
|
||||
} catch {
|
||||
nodeAvailable = false
|
||||
}
|
||||
return nodeAvailable
|
||||
}
|
||||
|
||||
export interface IsolatedVMExecutionRequest {
|
||||
code: string
|
||||
params: Record<string, unknown>
|
||||
@@ -171,6 +184,16 @@ async function ensureWorker(): Promise<void> {
|
||||
if (workerReadyPromise) return workerReadyPromise
|
||||
|
||||
workerReadyPromise = new Promise<void>((resolve, reject) => {
|
||||
if (!checkNodeAvailable()) {
|
||||
reject(
|
||||
new Error(
|
||||
'Node.js is required for code execution but was not found. ' +
|
||||
'Please install Node.js (v20+) from https://nodejs.org'
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
const currentDir = path.dirname(fileURLToPath(import.meta.url))
|
||||
const workerPath = path.join(currentDir, 'isolated-vm-worker.cjs')
|
||||
|
||||
@@ -179,53 +202,54 @@ async function ensureWorker(): Promise<void> {
|
||||
return
|
||||
}
|
||||
|
||||
worker = spawn(process.execPath, [workerPath], {
|
||||
stdio: ['ignore', 'pipe', 'inherit', 'ipc'],
|
||||
serialization: 'json',
|
||||
})
|
||||
import('node:child_process').then(({ spawn }) => {
|
||||
worker = spawn('node', [workerPath], {
|
||||
stdio: ['ignore', 'pipe', 'inherit', 'ipc'],
|
||||
serialization: 'json',
|
||||
})
|
||||
|
||||
worker.on('message', handleWorkerMessage)
|
||||
worker.on('message', handleWorkerMessage)
|
||||
|
||||
const startTimeout = setTimeout(() => {
|
||||
worker?.kill()
|
||||
worker = null
|
||||
workerReady = false
|
||||
workerReadyPromise = null
|
||||
reject(new Error('Worker failed to start within timeout'))
|
||||
}, 10000)
|
||||
const startTimeout = setTimeout(() => {
|
||||
worker?.kill()
|
||||
worker = null
|
||||
workerReady = false
|
||||
workerReadyPromise = null
|
||||
reject(new Error('Worker failed to start within timeout'))
|
||||
}, 10000)
|
||||
|
||||
const readyHandler = (message: unknown) => {
|
||||
if (
|
||||
typeof message === 'object' &&
|
||||
message !== null &&
|
||||
(message as { type?: string }).type === 'ready'
|
||||
) {
|
||||
workerReady = true
|
||||
clearTimeout(startTimeout)
|
||||
worker?.off('message', readyHandler)
|
||||
resolve()
|
||||
const readyHandler = (message: unknown) => {
|
||||
if (
|
||||
typeof message === 'object' &&
|
||||
message !== null &&
|
||||
(message as { type?: string }).type === 'ready'
|
||||
) {
|
||||
workerReady = true
|
||||
clearTimeout(startTimeout)
|
||||
worker?.off('message', readyHandler)
|
||||
resolve()
|
||||
}
|
||||
}
|
||||
}
|
||||
worker.on('message', readyHandler)
|
||||
worker.on('message', readyHandler)
|
||||
|
||||
worker.on('exit', (code) => {
|
||||
logger.warn('Isolated-vm worker exited', { code })
|
||||
if (workerIdleTimeout) {
|
||||
clearTimeout(workerIdleTimeout)
|
||||
workerIdleTimeout = null
|
||||
}
|
||||
worker = null
|
||||
workerReady = false
|
||||
workerReadyPromise = null
|
||||
for (const [id, pending] of pendingExecutions) {
|
||||
clearTimeout(pending.timeout)
|
||||
pending.resolve({
|
||||
result: null,
|
||||
stdout: '',
|
||||
error: { message: 'Worker process exited unexpectedly', name: 'WorkerError' },
|
||||
})
|
||||
pendingExecutions.delete(id)
|
||||
}
|
||||
worker.on('exit', () => {
|
||||
if (workerIdleTimeout) {
|
||||
clearTimeout(workerIdleTimeout)
|
||||
workerIdleTimeout = null
|
||||
}
|
||||
worker = null
|
||||
workerReady = false
|
||||
workerReadyPromise = null
|
||||
for (const [id, pending] of pendingExecutions) {
|
||||
clearTimeout(pending.timeout)
|
||||
pending.resolve({
|
||||
result: null,
|
||||
stdout: '',
|
||||
error: { message: 'Worker process exited unexpectedly', name: 'WorkerError' },
|
||||
})
|
||||
pendingExecutions.delete(id)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user