import { pathToFileURL } from 'node:url'; import { createOrchestratorApp } from './app.mjs'; import { createOrchestratorCheckpoint } from './checkpoint.mjs'; import { createExecutorJobPersistence } from './executor-job-store.mjs'; import { createLangGraphOrchestratorRuntime } from './runtime.mjs'; function positivePort(value, fallback = 8093) { const parsed = Number(value); if (!Number.isInteger(parsed) || parsed < 1 || parsed > 65535) return fallback; return parsed; } function envFlag(value, fallback = false) { const normalized = String(value ?? '').trim().toLowerCase(); if (!normalized) return fallback; return ['1', 'true', 'yes', 'on'].includes(normalized); } function csvList(value) { return [...new Set( String(value ?? '') .split(',') .map((item) => item.trim().toLowerCase()) .filter(Boolean), )]; } export async function startOrchestratorServer({ env = process.env, logger = console, } = {}) { const checkpoint = await createOrchestratorCheckpoint({ mode: env.MEMIND_ORCHESTRATOR_CHECKPOINT_MODE, connectionString: env.MEMIND_ORCHESTRATOR_DATABASE_URL, schema: env.MEMIND_ORCHESTRATOR_DATABASE_SCHEMA, }); let executorJobs; try { executorJobs = await createExecutorJobPersistence({ mode: env.MEMIND_ORCHESTRATOR_CHECKPOINT_MODE, connectionString: env.MEMIND_ORCHESTRATOR_DATABASE_URL, schema: env.MEMIND_ORCHESTRATOR_DATABASE_SCHEMA, }); } catch (error) { await Promise.allSettled([checkpoint.close()]); throw error; } const runtime = createLangGraphOrchestratorRuntime({ checkpointer: checkpoint.checkpointer, checkpointKind: checkpoint.kind, durable: checkpoint.durable, checkpointProbe: checkpoint.probe, executorJobStore: executorJobs.store, executorJobProbe: executorJobs.probe, executionEnabled: envFlag(env.MEMIND_ORCHESTRATOR_EXECUTION_ENABLED, false), enabledExecutors: csvList(env.MEMIND_ORCHESTRATOR_ENABLED_EXECUTORS), admissionConfig: { tenantAllowlist: csvList(env.MEMIND_ORCHESTRATOR_TENANT_ALLOWLIST), userAllowlist: csvList(env.MEMIND_ORCHESTRATOR_USER_ALLOWLIST), workspaceKindAllowlist: csvList( env.MEMIND_ORCHESTRATOR_WORKSPACE_KIND_ALLOWLIST ?? 'workspace-alias', ), maxGlobalActive: env.MEMIND_ORCHESTRATOR_MAX_GLOBAL_ACTIVE, maxTenantActive: env.MEMIND_ORCHESTRATOR_MAX_TENANT_ACTIVE, maxUserActive: env.MEMIND_ORCHESTRATOR_MAX_USER_ACTIVE, maxTenantPerMinute: env.MEMIND_ORCHESTRATOR_MAX_TENANT_PER_MINUTE, maxUserPerMinute: env.MEMIND_ORCHESTRATOR_MAX_USER_PER_MINUTE, }, }); const app = createOrchestratorApp({ runtime, serviceToken: env.MEMIND_ORCHESTRATOR_SERVICE_TOKEN, workerToken: env.MEMIND_ORCHESTRATOR_WORKER_TOKEN, }); const host = String(env.MEMIND_ORCHESTRATOR_HOST ?? '127.0.0.1').trim() || '127.0.0.1'; const port = positivePort(env.MEMIND_ORCHESTRATOR_PORT, 8093); let server; try { server = await new Promise((resolve, reject) => { const listening = app.listen(port, host, () => resolve(listening)); listening.once('error', reject); }); } catch (error) { await Promise.allSettled([executorJobs.close(), checkpoint.close()]); throw error; } logger.log(`[orchestrator] listening on http://${host}:${port} (${checkpoint.kind})`); async function close() { let serverCloseError = null; try { await new Promise((resolve, reject) => { server.close((error) => (error ? reject(error) : resolve())); }); } catch (error) { serverCloseError = error; } const results = await Promise.allSettled([ executorJobs.close(), checkpoint.close(), ]); if (serverCloseError) throw serverCloseError; const failed = results.find((result) => result.status === 'rejected'); if (failed) throw failed.reason; } return { app, server, runtime, checkpoint, executorJobs, close, }; } const isEntrypoint = process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href; if (isEntrypoint) { const running = await startOrchestratorServer(); const shutdown = async (signal) => { console.log(`[orchestrator] received ${signal}, shutting down`); await running.close(); process.exit(0); }; process.once('SIGINT', () => void shutdown('SIGINT')); process.once('SIGTERM', () => void shutdown('SIGTERM')); } export const orchestratorServerInternals = { csvList, envFlag, positivePort, };