UNPKG

life

Version:

Life.js is the first fullstack framework to build agentic web applications. It is minimal, extensible, and typesafe. Well, everything you love.

1,300 lines (1,287 loc) 170 kB
import { logLevelPriority, telemetryBrowserScopesDefinition } from "./chunk-5BLN2MK4.mjs"; import { ProcessStats, createTelemetryClient, pipeConsoleToTelemetryClient, telemetryNodeScopesDefinition } from "./chunk-MLK5YGGI.mjs"; import { TelemetryClient, canon, ns, package_default, stripAnsi, telemetrySignalSchema } from "./chunk-6CBODJTF.mjs"; import { prepareAgentConfig } from "./chunk-XNMZQAOR.mjs"; import { AsyncQueue, agentClientConfig, deepClone } from "./chunk-D2T23PCX.mjs"; import { importServerBuild } from "./chunk-CUJAJGIJ.mjs"; import { failure, isLifeError, lifeError, newId, obfuscateLifeError, success } from "./chunk-ZHBK6UTM.mjs"; import { __name } from "./chunk-2D3UJWOA.mjs"; // cli/run.ts import { Command as Command5 } from "commander"; // telemetry/helpers/formatting/terminal.ts import path from "path"; import chalk2 from "chalk"; import esbuild from "esbuild"; import z from "zod"; // cli/utils/theme.ts import chalk from "chalk"; var theme = { orange: "#E77823", gray: { light: "#bbbbbb", medium: "#888888", dark: "#444444" }, level: { fatal: "red", error: "red", warn: "#FFA500", info: "cyan", debug: "gray" } }; var themeChalk = { orange: chalk.hex(theme.orange), gray: { light: chalk.hex(theme.gray.light), medium: chalk.hex(theme.gray.medium), dark: chalk.hex(theme.gray.dark) }, level: { fatal: chalk.bgRed, error: chalk.red, info: chalk.cyan, debug: chalk.gray, warn: chalk.hex(theme.level.warn) } }; // telemetry/helpers/formatting/terminal.ts var isEsbuildError = /* @__PURE__ */ __name((error) => { if (error instanceof Error && "errors" in error) { const errorsSchema = z.array( z.object({ id: z.string().optional(), pluginName: z.string().optional(), text: z.string().optional() }) ); const { success: success2 } = errorsSchema.safeParse(error.errors); return success2; } return false; }, "isEsbuildError"); function formatErrorForTerminal(error) { let code = ""; let message = ""; let stack = ""; let after = ""; let processed = false; if (isLifeError(error)) { code = `LifeError (${chalk2.bold(error.code)})`; message = error.message; stack = error.stack ? error.stack.split("\n").slice(3).join("\n") : ""; if (error.cause) { after += formatErrorForTerminal(error.cause); const typedCause = error.cause; if (error.code === "Unknown" && typedCause?.stack) stack = ""; } processed = true; } else if (error instanceof z.ZodError) { code = "ZodError"; message = z.prettifyError(error); stack = error.stack ?? ""; if (stack.includes(" at ")) { stack = ` ${stack.split(" at ").slice(1).map((line) => ` at ${line}`).join("") ?? ""}`; } processed = true; } else if (isEsbuildError(error)) { const formatEsbuildMessage = /* @__PURE__ */ __name((msg) => esbuild.formatMessagesSync([msg], { kind: "error", color: true })?.[0]?.replace("\x1B[31m\u2718 \x1B[41;31m[\x1B[41;97mERROR\x1B[41;31m]\x1B[0m \x1B[1m", "")?.trim() ?? "", "formatEsbuildMessage"); try { const esbuildError = error; const formattedMessages = esbuildError.errors.map(formatEsbuildMessage); message = `BuildError: ${formattedMessages.join("\n\n")}`; processed = true; } catch (_) { } } if (!processed && error instanceof Error) { if ("name" in error && typeof error.name === "string") code = error.name; else if ("code" in error && typeof error.code === "string") code = error.code; if ("message" in error && typeof error.message === "string") message = error.message; else if ("reason" in error && typeof error.reason === "string") message = error.reason; if ("stack" in error && typeof error.stack === "string") stack = error.stack; stack = stack?.split("\n")?.filter((line) => !line.includes(error.message.trim()))?.join("\n") ?? ""; if (!code) code = "Unknown Error"; if (!message) message = "An unknown error occurred."; if (!stack) stack = ""; } if (error instanceof Error && error.cause && !isLifeError(error)) { after += `${formatErrorForTerminal(error.cause)}`; } stack = stack.replace(/\/[^\s\n\r:;,()[\]{}'"<>]+/g, (match) => { try { if (path.isAbsolute(match)) { const relativePath = path.relative(process.cwd(), match); if (relativePath.length < match.length) return relativePath; } return match; } catch { return match; } }); return `${code}${code ? ": " : ""}${message}${message ? " " : ""}${stack ? ` ${stack}` : ""}${after ? ` ${after}` : ""}`; } __name(formatErrorForTerminal, "formatErrorForTerminal"); function formatLogForTerminal(log) { let style; if (log.level === "fatal") style = { prefix: themeChalk.level.fatal.bold("\u2718"), color: themeChalk.level.fatal }; else if (log.level === "error") style = { prefix: themeChalk.level.error.bold("\u2718"), color: themeChalk.level.error }; else if (log.level === "warn") style = { prefix: themeChalk.level.warn.bold("\u25B2"), color: themeChalk.level.warn }; else if (log.level === "info") style = { prefix: themeChalk.level.info.bold("\u29BF"), color: themeChalk.level.info }; else style = { prefix: themeChalk.level.debug.bold("\u2234"), color: themeChalk.level.debug }; const scopeDefinition = telemetryNodeScopesDefinition?.[log.scope] ?? telemetryBrowserScopesDefinition?.[log.scope]; const scopeDisplayName = scopeDefinition?.displayName instanceof Function ? ( // biome-ignore lint/suspicious/noExplicitAny: fine here scopeDefinition.displayName(log.attributes) ) : scopeDefinition?.displayName; const scope = `${chalk2.gray(`[${chalk2.italic(scopeDisplayName ?? "Unknown")}]`)} `; const message = log.message || ""; const header = `${style.prefix} ${scope}${style.color ? style.color(message) : message}`; const error = formatErrorForTerminal(log.error); const errorColor = ["error", "fatal"].includes(log.level) ? themeChalk.level.error : themeChalk.level.warn; let output = header; if (error) output += ` ${errorColor.dim("-----")} ${errorColor(error)} ${errorColor.dim("-----")}`; return output; } __name(formatLogForTerminal, "formatLogForTerminal"); // cli/commands/build/index.ts import { resolve as resolve2 } from "path"; import { Command } from "commander"; // cli/utils/header.ts import chalk4 from "chalk"; // cli/utils/version.ts import chalk3 from "chalk"; import { getLatestVersion } from "fast-npm-meta"; async function getVersion() { const currentVersion = package_default.version; try { const latestVersionData = await getLatestVersion("life"); const latestVersion = latestVersionData.version; return { current: currentVersion, latest: latestVersion ?? void 0, hasUpdate: currentVersion !== latestVersion }; } catch { return { current: currentVersion, hasUpdate: false }; } } __name(getVersion, "getVersion"); function formatVersion(versionInfo) { const hasUpdate = versionInfo.hasUpdate && versionInfo.latest; const raw = hasUpdate ? `${versionInfo.current} (\u2191 ${versionInfo.latest})` : versionInfo.current; const output = hasUpdate ? `${themeChalk.gray.medium(versionInfo.current)} ${chalk3.green(chalk3.bold(`(\u2191 ${versionInfo.latest})`))}` : themeChalk.gray.medium(versionInfo.current); return { raw, output }; } __name(formatVersion, "formatVersion"); // cli/utils/header.ts async function generateHeader(name) { const nameLength = `Life.js ${name}`.length; const formattedVersion = formatVersion(await getVersion()); const gap = 13; const padding = 1; const headerSeparator = chalk4.gray( "\u2500".repeat(nameLength + gap + formattedVersion.raw.length + padding * 2) ); return ` ${headerSeparator} ${" ".repeat(padding)}${themeChalk.gray.medium("Life.js")} ${themeChalk.orange(chalk4.italic(name))}${" ".repeat(gap)}${formattedVersion.output}${" ".repeat(padding)} ${headerSeparator} `; } __name(generateHeader, "generateHeader"); // compiler/index.ts import { createHash } from "crypto"; import { access, mkdir, readFile, rm, writeFile } from "fs/promises"; import os from "os"; import path3, { dirname, join, relative } from "path"; import { Lang, parseAsync as parseAsync2 } from "@ast-grep/napi"; import chalk5 from "chalk"; import chokidar from "chokidar"; import esbuild2 from "esbuild"; import { globbySync } from "globby"; import ts from "typescript"; // compiler/helpers/dependencies-map.ts import fs from "fs/promises"; import path2 from "path"; import { parseAsync } from "oxc-parser"; import { walk } from "oxc-walker"; import resolve from "resolve"; var EXTENSIONS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs", ".json"]; var detectLanguage = /* @__PURE__ */ __name((f) => { const ext = path2.extname(f).toLowerCase(); if (ext === ".tsx") return "tsx"; if (ext === ".ts" || ext === ".mts" || ext === ".cts") return "ts"; if (ext === ".jsx") return "jsx"; return "js"; }, "detectLanguage"); var resolveLocalFrom = /* @__PURE__ */ __name((spec, basedir) => { try { const resolved = resolve.sync(spec, { basedir, extensions: EXTENSIONS, preserveSymlinks: true, includeCoreModules: false }); return resolved.includes("/node_modules/") ? null : resolved; } catch { return null; } }, "resolveLocalFrom"); var isTypeOnlyImport = /* @__PURE__ */ __name((node) => { if (node.type === "ImportDeclaration" && node.importKind === "type") { return true; } if (node.type === "ImportDeclaration" && node.specifiers) { if (node.specifiers.length === 0) { return false; } const hasValueImports = node.specifiers.some((spec) => { if (spec.type === "ImportDefaultSpecifier" || spec.type === "ImportNamespaceSpecifier") { return true; } return spec.type === "ImportSpecifier" && spec.importKind !== "type"; }); return !hasValueImports; } return false; }, "isTypeOnlyImport"); var getDependenciesMap = /* @__PURE__ */ __name(async (entries, exclude = [], skipTypeOnlyDependencies = false) => { const output = /* @__PURE__ */ new Set(); const entriesArray = Array.isArray(entries) ? entries : [entries]; for (const p of entriesArray) { if (!path2.isAbsolute(p)) return failure({ code: "Validation", message: `Provided entry path must be absolute: ${p}` }); } const entriesSet = new Set(entriesArray.map((p) => path2.resolve(p))); const excludeArray = Array.isArray(exclude) ? exclude : [exclude]; for (const p of excludeArray) { if (p && !path2.isAbsolute(p)) return failure({ code: "Validation", message: `Provided exclude path must be absolute: ${p}` }); } const excludeSet = new Set(excludeArray.filter(Boolean).map((p) => path2.resolve(p))); const queue = [...entriesSet]; const visited = /* @__PURE__ */ new Set(); while (queue.length) { const file = queue.pop(); if (!file) throw new Error("Shouldn't happen"); if (visited.has(file) || excludeSet.has(file)) continue; visited.add(file); let content; try { content = await fs.readFile(file, "utf8"); } catch { continue; } let ast; try { ast = await parseAsync(file, content, { sourceType: "module", lang: detectLanguage(file) }); } catch { continue; } const specifiers = /* @__PURE__ */ new Set(); walk(ast.program, { enter(node) { if (node.type === "ImportDeclaration" && node.source?.value) { if (skipTypeOnlyDependencies && isTypeOnlyImport(node)) { return; } specifiers.add(node.source.value); } if ((node.type === "ExportAllDeclaration" || node.type === "ExportNamedDeclaration") && node.source?.value) { if (skipTypeOnlyDependencies && node.exportKind === "type") { return; } specifiers.add(node.source.value); } if (node.type === "ImportExpression" && node.source?.type === "Literal" && typeof node.source.value === "string") { specifiers.add(node.source.value); } if (node.type === "CallExpression" && node.arguments?.length === 1) { const arg = node.arguments[0]; const literalArg = arg && arg.type === "Literal" && typeof arg.value === "string" ? arg.value : null; if (literalArg && node.callee.type === "Identifier" && node.callee.name === "require") { specifiers.add(literalArg); } else if (literalArg && node.callee.type === "MemberExpression" && node.callee.object.type === "Identifier" && node.callee.object.name === "require" && node.callee.property.type === "Identifier" && node.callee.property.name === "resolve") { specifiers.add(literalArg); } } } }); const basedir = path2.dirname(file); for (const spec of specifiers) { const resolved = resolveLocalFrom(spec, basedir); if (!resolved || excludeSet.has(resolved)) continue; if (!entriesSet.has(resolved)) output.add(resolved); if (!visited.has(resolved)) queue.push(resolved); } } return success(Array.from(output)); }, "getDependenciesMap"); // compiler/options.ts import z2 from "zod"; var compilerOptionsSchema = z2.object({ projectDirectory: z2.string(), outputDirectory: z2.string().prefault(".life"), watch: z2.boolean().prefault(false), stopOnError: z2.boolean().prefault(true) }); // compiler/index.ts var EXCLUDED_DEFAULTS = ["**/node_modules/**", "**/build/**", "**/generated/**", "**/dist/**"]; var LifeCompiler = class { static { __name(this, "LifeCompiler"); } options; hashes = /* @__PURE__ */ new Map(); watcher = null; paths = { configs: /* @__PURE__ */ new Set(), servers: /* @__PURE__ */ new Set(), clients: /* @__PURE__ */ new Set(), dependencies: /* @__PURE__ */ new Map(), // entryPath -> dependencies serverBuilds: /* @__PURE__ */ new Map(), // entryPath -> buildPath clientBuilds: /* @__PURE__ */ new Map() // entryPath -> buildPath }; // Language Service for faster type extraction telemetry; #serverBundleContext = null; #languageService; #serviceHost; #virtualFiles = /* @__PURE__ */ new Map(); #pluginNamesCache = /* @__PURE__ */ new Map(); constructor(options) { this.options = compilerOptionsSchema.parse(options); if (this.options.watch && options?.stopOnError === void 0) { this.options.stopOnError = false; } this.telemetry = createTelemetryClient("compiler", { watch: this.options.watch }); this.options.outputDirectory = this.options.outputDirectory.startsWith("/") ? this.options.outputDirectory : join(this.options.projectDirectory, this.options.outputDirectory); } async start() { return await this.telemetry.trace("start()", async (span) => { try { span.log.info({ message: "Starting compiler." }); span.log.debug({ message: `Project directory: ${this.options.projectDirectory}` }); span.log.debug({ message: `Output directory: ${this.options.outputDirectory}` }); this.telemetry.counter("compiler_started").increment(); await this.telemetry.trace("initial-compilation", async (spanCompilation) => { await Promise.all([this.ensureBuildDirectory(), this.linkBuildDirectory()]); const entryPaths = globbySync( [ "**/agent/{server.ts,client.ts}", "**/agents/*/{server.ts,client.ts}", "**/life.config.ts" ], { cwd: this.options.projectDirectory, ignore: EXCLUDED_DEFAULTS, dot: false, onlyFiles: true, absolute: true, gitignore: true, unique: true } ); await Promise.all(entryPaths.map(async (p) => this.refreshEntryPathDependencies(p))); const configResults = await Promise.all( entryPaths.filter((p) => p.endsWith("life.config.ts")).map( async (absPath) => await this.processFileEvent({ action: "added", absPath, type: "config", noTimingLogs: true }) ) ); const agentResults = await Promise.all([ ...entryPaths.filter((p) => p.endsWith("server.ts")).map( async (absPath) => await this.processFileEvent({ action: "added", absPath, type: "server", noTimingLogs: true }) ), ...entryPaths.filter((p) => p.endsWith("client.ts")).map( async (absPath) => await this.processFileEvent({ action: "added", absPath, type: "client", noTimingLogs: true }) ) ]); const results = [...configResults, ...agentResults]; spanCompilation.end(); const duration = spanCompilation.getData().duration; const errorsCount = results.filter((r) => Boolean(r?.[0])).length; const hasAgents = entryPaths.filter((p) => p.endsWith("server.ts")).length > 0; if (hasAgents) { span.log.info({ message: `Initial compilation in ${chalk5.bold(`${ns.toMs(duration)}ms`)}. ${errorsCount > 0 ? chalk5.red(`(${chalk5.bold(errorsCount)} error${errorsCount > 1 ? "s" : ""})`) : chalk5.dim("(no errors)")}` }); } else span.log.info({ message: "No agent server to compile yet. Create a first `agent/server.ts` file in your project." }); }); if (this.options.watch) { this.watchEntryPaths(); span.log.info({ message: "Watching for changes..." }); const handleShutdown = /* @__PURE__ */ __name(async (signal) => { console.log(""); this.telemetry.log.info({ message: `Received ${signal}, shutting down gracefully...` }); await this.stop(); }, "handleShutdown"); process.once("SIGINT", () => handleShutdown("SIGINT")); process.once("SIGTERM", () => handleShutdown("SIGTERM")); } else await this.stop(); return success(); } catch (error) { return failure({ code: "Unknown", cause: error }); } }); } #stopStarted = false; async stop() { return await this.telemetry.trace("stop()", async (span) => { if (this.#stopStarted) return; this.#stopStarted = true; span.log.info({ message: "Stopping compiler." }); this.#serverBundleContext?.dispose(); this.#serverBundleContext = null; await this.watcher?.close(); this.watcher = null; await this.telemetry.flushConsumers(); }); } async ensureBuildDirectory() { await Promise.all([ mkdir(path3.join(this.options.outputDirectory, "server", "raw"), { recursive: true }), mkdir(path3.join(this.options.outputDirectory, "server", "dist"), { recursive: true }), mkdir(path3.join(this.options.outputDirectory, "server", "signal"), { recursive: true }), mkdir(path3.join(this.options.outputDirectory, "client"), { recursive: true }) ]); } async linkBuildDirectory() { const generatedClientPath = join(this.options.outputDirectory, "client", "index.ts"); const generatedServerPath = join(this.options.outputDirectory, "server", "dist", "index.js"); const [errDistDir, distDir] = await this.findDistDirectory(); if (errDistDir) return failure(errDistDir); const distFiles = globbySync("**/*", { cwd: distDir, onlyFiles: true, absolute: false }); const CODE_FILE_EXTENSIONS = [".js", ".mjs", ".cjs", ".ts", ".mts", ".cts", ".jsx", ".tsx"]; const codeFiles = distFiles.filter((file) => { const ext = path3.extname(file).toLowerCase(); return CODE_FILE_EXTENSIONS.includes(ext); }); await Promise.all( codeFiles.map(async (file) => { const filePath = join(distDir, file); const content = await readFile(filePath, "utf-8"); const clientPath = relative(dirname(filePath), generatedClientPath); const serverPath = relative(dirname(filePath), generatedServerPath); const updatedContent = content.replaceAll(/"LIFE()_CLIENT_BUILD_PATH"/g, `"${clientPath}"`).replaceAll(/String\("LIFE()_CLIENT_BUILD_MODULE"\)/g, `import("${clientPath}")`).replaceAll(/"LIFE()_CLIENT_BUILD_MODULE"/g, `import("${clientPath}")`).replaceAll(/"LIFE()_SERVER_BUILD_PATH"/g, `"${serverPath}"`).replaceAll(/"LIFE()_BUILD_MODE"/g, '"production"').replaceAll(/'LIFE()_CLIENT_BUILD_PATH'/g, `'${clientPath}'`).replaceAll(/String\('LIFE()_CLIENT_BUILD_MODULE'\)/g, `import("${clientPath}")`).replaceAll(/'LIFE()_CLIENT_BUILD_MODULE'/g, `import("${clientPath}")`).replaceAll(/'LIFE()_SERVER_BUILD_PATH'/g, `'${serverPath}'`).replaceAll(/'LIFE()_BUILD_MODE'/g, "'production'"); if (updatedContent !== content) await writeFile(filePath, updatedContent, "utf-8"); }) ); } async getPathDisplayName(_path, type) { const relativePath = path3.relative(this.options.projectDirectory, _path); if (["config", "dependency", "unknown"].includes(type)) return relativePath; const clientContent = await readFile(_path, "utf-8"); const ast = await parseAsync2(Lang.TypeScript, clientContent); const root = ast.root(); const defineCall = root.find({ rule: { kind: "call_expression", any: [ { pattern: `defineAgent${type === "client" ? "Client" : ""}<$$$>($ARG)` }, { pattern: `defineAgent${type === "client" ? "Client" : ""}($ARG)` } ] } }); if (!defineCall) return relativePath; let name = defineCall?.getMatch("ARG")?.text() ?? null; if (name) name = JSON.parse(name); else return relativePath; return name; } /** * Main compiler entry point. Used to process a file. * @param params - The parameters for the file event. * @returns The result of the file event. */ async processFileEvent({ type, action, absPath, noCache = false, noTimingLogs = false }) { const result = await this.telemetry.trace("processFileEvent()", async (span) => { span.setAttributes({ action, relPath: absPath }); const emitTimingLogs = /* @__PURE__ */ __name(async (recompiled) => { if (noTimingLogs) return; if (!["server", "client"].includes(type)) return; span.end(); const name = await this.getPathDisplayName(absPath, type); this.telemetry.log.info({ message: `Agent ${type} '${chalk5.bold.italic(name)}' ${recompiled ? "re-compiled" : "compiled"} in ${chalk5.bold(`${ns.toMs(span.getData().duration)}ms`)}.`, attributes: { type, name } }); }, "emitTimingLogs"); absPath = this.ensureAbsolute(absPath); if (type === "unknown") return success(); if (action === "added") { this.hashes.set(absPath, await this.hashFile(absPath)); span.log.debug({ message: `Added '${type}' path: ${absPath}`, attributes: { path: absPath } }); if (type === "config") return await this.onAddedConfig(absPath); else if (type === "server") { const res = await this.onAddedServer(absPath); if (!res?.[0]) await emitTimingLogs(false); return res; } else if (type === "client") { const res = await this.onAddedClient(absPath); if (!res?.[0]) await emitTimingLogs(false); return res; } else if (type === "dependency") return failure({ code: "Conflict", message: "'added' action is not supported for dependency paths. Shouldn't happen." }); } else if (action === "removed") { this.hashes.delete(absPath); span.log.debug({ message: `Removed '${type}' path: ${absPath}`, attributes: { path: absPath } }); if (type === "config") return await this.onRemovedConfig(absPath); else if (type === "server") return await this.onRemovedServer(absPath); else if (type === "client") return await this.onRemovedClient(absPath); else if (type === "dependency") return await this.onRemovedDependency(absPath); } else if (action === "changed") { const newHash = await this.hashFile(absPath); if (newHash === this.hashes.get(absPath) && !noCache) return success(); this.hashes.set(absPath, newHash); span.log.debug({ message: `Changed '${type}' path: ${absPath}`, attributes: { path: absPath } }); if (type !== "dependency") await this.refreshEntryPathDependencies(absPath); if (type === "config") return await this.onChangedConfig(absPath); else if (type === "server") { const res = await this.onChangedServer(absPath); if (!res?.[0]) await emitTimingLogs(true); return res; } else if (type === "client") { const res = await this.onChangedClient(absPath); if (!res?.[0]) await emitTimingLogs(true); return res; } else if (type === "dependency") return await this.onChangedDependency(absPath); } throw new Error("Invalid action. Shouldn't happen."); }); const [error] = result; if (error) { const displayName = await this.getPathDisplayName(absPath, type); let baseMessage = "Failed to compile "; if (["client", "server"].includes(type)) baseMessage += `agent ${type} '${chalk5.bold.italic(displayName)}'.`; else baseMessage += `'${chalk5.bold.italic(displayName)}' ${type} file.`; const relativePath = path3.relative(this.options.projectDirectory, absPath); if (this.options.stopOnError) { this.telemetry.log.error({ message: `${baseMessage} Path: ${relativePath}`, error }); await this.stop(); } else this.telemetry.log.warn({ message: `${baseMessage} It has been ignored. Path: ${relativePath}`, error }); } return result; } // Configs Events Handlers async onAddedConfig(configPath) { return await this.telemetry.trace( "onAddedConfig()", async () => { try { this.paths.configs.add(configPath); return await this.onChangedConfig(configPath); } catch (error) { return failure({ code: "Unknown", cause: error }); } }, { attributes: { configPath } } ); } async onRemovedConfig(configPath) { return await this.telemetry.trace( "onRemovedConfig()", async () => { try { this.paths.configs.delete(configPath); return await this.onChangedConfig(configPath); } catch (error) { return failure({ code: "Unknown", cause: error }); } }, { attributes: { configPath } } ); } async onChangedConfig(configPath) { return await this.telemetry.trace( "onChangedConfig()", async () => { try { const configContent = await readFile(configPath, "utf-8"); const ast = await parseAsync2(Lang.TypeScript, configContent); const root = ast.root(); const defineConfigCall = root.find({ rule: { kind: "call_expression", pattern: "defineConfig($ARG)" } }); if (!defineConfigCall) { return failure({ code: "Validation", message: "Config file does not contain a defineConfig() call.", attributes: { path: configPath } }); } const exportDefaultStatement = root.find({ rule: { kind: "export_statement", pattern: "export default", has: { regex: "defineConfig(.*)" } } }); if (!exportDefaultStatement) { return failure({ code: "Validation", message: `Config file has defineConfig() but doesn't export it as default. Use \`export default defineConfig(...)\` instead.`, attributes: { path: configPath } }); } const affected = Array.from(this.paths.servers).filter( (p) => this.isEntryPathTouchedByConfig(p, configPath) ); await Promise.all( affected.map(async (serverPath) => { const absPath = this.ensureAbsolute(serverPath); return await this.processFileEvent({ action: "changed", absPath, type: "server", noCache: true }); }) ); return success(); } catch (error) { return failure({ code: "Unknown", cause: error }); } }, { attributes: { configPath } } ); } // Agent Servers Events Handlers async onAddedServer(serverPath) { return await this.telemetry.trace( "onAddedServer()", async () => { try { this.paths.servers.add(serverPath); return await this.onChangedServer(serverPath); } catch (error) { return failure({ code: "Unknown", cause: error }); } }, { attributes: { serverPath } } ); } async onRemovedServer(serverPath) { return await this.telemetry.trace( "onRemovedServer()", async () => { try { this.paths.servers.delete(serverPath); const buildPath = this.paths.serverBuilds.get(serverPath); if (buildPath) { this.paths.serverBuilds.delete(serverPath); await rm(buildPath); } const res = await this.generateServerBundle(); if (!res?.[0]) return res; const name = this.getPathDisplayName(serverPath, "server"); const signalPath = path3.join( this.options.outputDirectory, "server", "signal", `${name}.txt` ); if (await this.fileExists(signalPath)) { await writeFile(signalPath, "removed", "utf-8"); } return success(); } catch (error) { return failure({ code: "Unknown", cause: error }); } }, { attributes: { serverPath } } ); } async onChangedServer(serverPath) { return await this.telemetry.trace( "onChangedServer()", async (span) => { try { const serverContent = await readFile(serverPath, "utf-8"); const ast = await parseAsync2(Lang.TypeScript, serverContent); const root = ast.root(); const defineAgentCall = root.find({ rule: { kind: "call_expression", pattern: "defineAgent($ARG)" } }); if (!defineAgentCall) { return failure({ code: "Validation", message: `Agent server '${chalk5.bold.italic(path3.relative(this.options.projectDirectory, serverPath))}' doesn't contain a ${chalk5.bold.italic("defineAgent(...)")} call.`, attributes: { serverPath } }); } const exportDefaultStatement = root.find({ rule: { kind: "export_statement", pattern: "export default", has: { regex: "defineAgent(.*)" } } }); if (!exportDefaultStatement) { return failure({ code: "Validation", message: `Agent server '${chalk5.bold.italic(path3.relative(this.options.projectDirectory, serverPath))}' doesn't export ${chalk5.bold.italic("defineAgent(...)")} call as default. Use ${chalk5.bold.italic("export default defineAgent(...)")}.`, attributes: { serverPath } }); } let name = defineAgentCall?.getMatch("ARG")?.text() ?? null; if (name) name = JSON.parse(name); if (!name) { return failure({ code: "Validation", message: `Agent server '${chalk5.bold.italic(name)}' has ${chalk5.bold.italic("defineAgent()")} but doesn't provide a name. Use ${chalk5.bold.italic("defineAgent(<name>)")}.`, attributes: { serverPath } }); } const configPaths = []; for (const configPath of this.paths.configs) { if (this.isEntryPathTouchedByConfig(serverPath, configPath)) configPaths.push(configPath); } configPaths.sort((a, b) => b.length - a.length); const treeFiles = /* @__PURE__ */ new Set([serverPath, ...configPaths]); for (const file of deepClone(treeFiles)) { const deps = this.paths.dependencies.get(file); if (deps) for (const dep of deps) treeFiles.add(dep); } const treeHashes = Array.from(treeFiles).map((file) => this.hashes.get(file)); const filteredTreeHashes = treeHashes.filter((hash) => hash !== void 0); if (treeHashes.length !== filteredTreeHashes.length) { span.log.warn({ message: "Some tree files have no hash. Shouldn't happen.", attributes: { treeFiles } }); } const sha = createHash("md5").update(treeHashes.join(":")).digest("hex"); const buildPath = path3.join(this.options.outputDirectory, "server", "raw", `${name}.ts`); const relServerPath = path3.relative(path3.dirname(buildPath), serverPath); const relConfigPaths = configPaths.map( (configPath) => path3.relative(path3.dirname(buildPath), configPath) ); const content = ` ${relConfigPaths.map((configPath, i) => `import config${i} from "${configPath}";`).join("\n")} import agent from "${relServerPath}"; export default { definition: agent.def, globalConfigs: [${configPaths.map((_, i) => `config${i}`).join(", ")}], sha: "${sha}" } as const; `.trim(); await writeFile(buildPath, content, "utf-8"); this.paths.serverBuilds.set(serverPath, buildPath); const [errBundle] = await this.generateServerBundle(); if (errBundle) return failure(errBundle); const signalPath = path3.join( this.options.outputDirectory, "server", "signal", `${name}.txt` ); await writeFile(signalPath, sha, "utf-8"); return success(); } catch (error) { return failure({ code: "Unknown", cause: error }); } }, { attributes: { serverPath } } ); } // Agent Clients Events Handlers async onAddedClient(clientPath) { return await this.telemetry.trace( "onAddedClient()", async () => { try { this.paths.clients.add(clientPath); return await this.onChangedClient(clientPath); } catch (error) { return failure({ code: "Unknown", cause: error }); } }, { attributes: { clientPath } } ); } async onRemovedClient(clientPath) { return await this.telemetry.trace( "onRemovedClient()", async () => { try { this.paths.clients.delete(clientPath); const buildPath = this.paths.clientBuilds.get(clientPath); if (buildPath) { this.paths.clientBuilds.delete(clientPath); await rm(buildPath); } return await this.onChangedClient(clientPath); } catch (error) { return failure({ code: "Unknown", cause: error }); } }, { attributes: { clientPath } } ); } async onChangedClient(clientPath) { return await this.telemetry.trace( "onChangedClient()", async () => { try { const clientContent = await readFile(clientPath, "utf-8"); const ast = await parseAsync2(Lang.TypeScript, clientContent); const root = ast.root(); const defineAgentClientCall = root.find({ rule: { kind: "call_expression", any: [ { pattern: "defineAgentClient<$$$>($ARG)" }, { pattern: "defineAgentClient($ARG)" } ] } }); if (!defineAgentClientCall) { return failure({ code: "Validation", message: `Agent client '${chalk5.bold.italic(path3.relative(this.options.projectDirectory, clientPath))}' doesn't contain a ${chalk5.bold.italic("defineAgentClient(...)")} call. It has been ignored.`, attributes: { clientPath } }); } const exportDefaultStatement = root.find({ rule: { kind: "export_statement", pattern: "export default", has: { regex: "defineAgentClient(<.*>)?(.*)" } } }); if (!exportDefaultStatement) { return failure({ code: "Validation", message: `Agent client '${chalk5.bold.italic(path3.relative(this.options.projectDirectory, clientPath))}' doesn't export ${chalk5.bold.italic("defineAgentClient(...)")} call as default. It has been ignored. Use ${chalk5.bold.italic("export default defineAgentClient(...)")}.`, attributes: { clientPath } }); } let name = defineAgentClientCall?.getMatch("ARG")?.text() ?? null; if (name) name = JSON.parse(name); if (!name) { return failure({ code: "Validation", message: `Agent client '${chalk5.bold.italic(path3.relative(this.options.projectDirectory, clientPath))}' has ${chalk5.bold.italic("defineAgentClient()")} but doesn't provide a name. It has been ignored. Use ${chalk5.bold.italic("defineAgentClient(<name>)")}.`, attributes: { clientPath } }); } const pluginNames = await this.extractClientPluginNames(clientPath); const plugins = pluginNames.map( (pluginName) => ` "${pluginName}": { def: p["${pluginName}"], $types: { atoms: (mock as typeof p["${pluginName}"]["atoms"])<PC["${pluginName}"]>(null as any), class: (mock as typeof p["${pluginName}"]["class"])<PC["${pluginName}"]>(null as any), clientConfig: {} as PC["${pluginName}"]["client"], serverConfig: {} as PC["${pluginName}"]["server"], } }` ).join(",\n"); const buildPath = path3.join(this.options.outputDirectory, "client", `${name}.ts`); const relClientPath = path3.relative(path3.dirname(buildPath), clientPath); const content = `import agentClient from "${relClientPath.replace(".ts", "")}"; type SC = typeof agentClient["def"]["$serverDef"]["pluginConfigs"]; type CC = typeof agentClient["def"]["pluginConfigs"]; type PC = { ${pluginNames.map((pluginName) => ` "${pluginName}": { client: CC["${pluginName}"], server: SC extends { "${pluginName}": unknown } ? SC["${pluginName}"] : never }`).join(",\n")} } const p = { ${pluginNames.map((pluginName) => ` "${pluginName}": agentClient.def.plugins.find(p => p.name === "${pluginName}")!`).join(",\n")} } as const; const mock = (() => void 0) as any; export default { definition: agentClient.def, plugins: { ${plugins} } } as const; `.trim(); await writeFile(buildPath, content, "utf-8"); this.paths.clientBuilds.set(clientPath, buildPath); const [errBundle] = await this.generateClientBundle(); if (errBundle) return failure(errBundle); return success(); } catch (error) { return failure({ code: "Unknown", cause: error }); } }, { attributes: { clientPath } } ); } // Dependency Paths Events Handlers async onRemovedDependency(dependencyPath) { return await this.telemetry.trace( "onRemovedDependency()", async () => { try { return await this.onChangedDependency(dependencyPath); } catch (error) { return failure({ code: "Unknown", cause: error }); } }, { attributes: { dependencyPath } } ); } async onChangedDependency(dependencyPath) { return await this.telemetry.trace( "onChangedDependency()", async () => { try { const affected = /* @__PURE__ */ new Set(); for (const [entryPath, dependencies] of this.paths.dependencies) { if (dependencies.has(dependencyPath)) affected.add(entryPath); } await Promise.all( Array.from(affected).map((p) => { const absPath = this.ensureAbsolute(p); const type = this.getPathType(absPath); return this.processFileEvent({ action: "changed", absPath, type, noCache: true }); }) ); return success(); } catch (error) { return failure({ code: "Unknown", cause: error }); } }, { attributes: { dependencyPath } } ); } watchEntryPaths() { return this.telemetry.trace("watchEntryPaths()", () => { function isMacOS26_1_x() { if (process.platform !== "darwin") return false; const release = os.release(); const majorVersion = Number.parseInt(release?.split(".")[0] ?? "0", 10) ?? 0; const minorVersion = Number.parseInt(release?.split(".")[1] ?? "0", 10) ?? 0; return majorVersion >= 25 && minorVersion >= 1; } __name(isMacOS26_1_x, "isMacOS26_1_x"); const enableFSEventsWorkaround = isMacOS26_1_x(); this.watcher = chokidar.watch(".", { cwd: this.options.projectDirectory, ignoreInitial: true, ignored: EXCLUDED_DEFAULTS, awaitWriteFinish: { stabilityThreshold: 20, pollInterval: 5 }, // Apply workaround on affected macOS versions ...enableFSEventsWorkaround && { usePolling: true, interval: 100 } }); const onWatcherEventFn = /* @__PURE__ */ __name((action) => (p) => { const absPath = this.ensureAbsolute(p); const type = this.getPathType(absPath); this.processFileEvent({ action, absPath, type }); }, "onWatcherEventFn"); this.watcher.on("add", onWatcherEventFn("added")); this.watcher.on("unlink", onWatcherEventFn("removed")); this.watcher.on("change", onWatcherEventFn("changed")); }); } async refreshEntryPathDependencies(entryPath) { const absPath = this.ensureAbsolute(entryPath); const exclude = []; if (this.getPathType(absPath) === "client") { exclude.push(absPath.replace("/client.ts", "/server.ts")); } const [error, dependencies] = await getDependenciesMap(absPath, exclude, true); if (error) { this.telemetry.log.error({ message: "Obtaining entry path dependencies failed.", error, attributes: { entryPath } }); return; } this.paths.dependencies.set(entryPath, new Set(dependencies)); await Promise.all(dependencies.map(async (d) => this.hashes.set(d, await this.hashFile(d)))); } getPathType(absPath) { const pathParts = absPath.split("/"); const parentDir = pathParts.at(-2); const grandParentDir = pathParts.at(-3); if (absPath.endsWith("/life.config.ts")) return "config"; else if ( // Match only agent/* or agents/<name>/* (parentDir === "agent" || grandParentDir === "agents") && // Exclude false positives if a plugins folder is present at the root of agents/ !absPath.includes("/plugins/") ) { if (absPath.endsWith("/server.ts")) return "server"; else if (absPath.endsWith("/client.ts")) return "client"; } for (const dependencies of this.paths.dependencies.values()) { if (dependencies.has(absPath)) return "dependency"; } return "unknown"; } ensureAbsolute(p) { return path3.resolve(this.options.projectDirectory, p); } async hashFile(filePath) { const content = await readFile(filePath, "utf-8"); return createHash("md5").update(content).digest("hex"); } isEntryPathTouchedByConfig(entryPath, configPath) { const configDir = path3.dirname(configPath); const relativePath = path3.relative(configDir, entryPath); return !relativePath.startsWith(".."); } /** * Finds the absolute path to node_modules/life/dist/ * @param startPath - The path to start searching from * @returns The absolute path to node_modules/life/dist/ */ async findDistDirectory() { const currentFilePath = import.meta.url.replace("file://", ""); let packageRoot = null; let searchDir = dirname(currentFilePath); for (let i = 0; i < 10; i++) { const packageJsonPath = join(searchDir, "package.json"); if (await this.fileExists(packageJsonPath)) { const packageJson = JSON.parse(await readFile(packageJsonPath, "utf-8")); if (packageJson.name === "life") { packageRoot = searchDir; break; } } searchDir = dirname(searchDir); } if (!packageRoot) return failure({ code: "NotFound", message: "Could not find life package root." }); const distDir = join(packageRoot, "dist"); if (!await this.fileExists(distDir)) return failure({ code: "NotFound", message: "Could not find life dist directory." }); return success(distDir); } // ------------------------------------- async generateServerBundle() { return await this.telemetry.trace("generateServerBundle()", async () => { try { const serversMap = {}; for (const serverPath of this.paths.serverBuilds.values()) { const name = path3.basename(serverPath, ".ts"); serversMap[name] = serverPath; } const indexPath = path3.join(this.options.outputDirectory, "server", "raw", "index.ts"); const indexContent = `${Object.entries(serversMap).map( ([name, p]) => `import ${name} from "./${path3.relative(path3.dirname(indexPath), p).replace(".ts", "")}";` ).join("\n")} export default { ${Object.keys(serversMap).map((name) => `"${name}": ${name}`).join(",\n")} } `.trim(); await writeFile(indexPath, indexContent, "utf-8"); if (this.#serverBundleContext) await this.#serverBundleContext.cancel(); else { this.#serverBundleContext = await esbuild2.context({ entryPoints: [indexPath], outdir: path3.join(this.options.outputDirectory, "server", "dist"), bundle: true, format: "esm", platform: "node", target: "node20", packages: "external", keepNames: true, jsx: "automatic", write: true, logLevel: "silent", treeShaking: true, loader: { ".node": "file" }, minify: true }); } await this.#serverBundleContext.rebuild(); return success(); } catch (error) { return failure({ code: "Unknown", cause: error }); } }); } async generateClientBundle() { return await this.telemetry.trace("generateClientBundle()", async () => { try { const clientsMap = {}; for (const clientPath of this.paths.clientBuilds.values()) { const name = path3.basename(clientPath, ".ts"); clientsMap[name] = clientPath; } const indexPath = path3.join(this.options.outputDirectory, "client", "index.ts"); const indexContent = `${Object.entries(clientsMap).map( ([name, p]) => `import ${name} from "./${path3.relative(path3.dirname(indexPath), p).replace(".ts", "")}";` ).join("\n")} export default { ${Object.keys(clientsMap).map((name) => `"${name}": ${name}`).join(",\n")} } `.trim(); await writeFile(indexPath, indexContent, "utf-8"); return success(); } catch (error) { return failure({ code: "Unknown", cause: error }); } }); } async fileExists(filePath) { try { await access(filePath); return true; } catch { return false; } } fileVersions = /* @__PURE__ */ new Map(); initLanguageService() { if (this.#languageService) return; const configPath = ts.findConfigFile( this.options.projectDirectory, ts.sys.fileExists, "tsconfig.json" ); if (!configPath) return; const { config } = ts.readConfigFile(configPath, ts.sys.readFile); const parsedConfig = ts.parseJsonConfigFileContent( config, ts.sys, this.options.projectDirectory ); parsedConfig.options.skipLibCheck = true; parsedConfig.options.skipDefaultLibCheck = true; this.#serviceHost = { getScriptFileNames: /* @__PURE__ */ __name(() => Array.from(this.#virtualFiles.keys()), "getScriptFileNames"), getScriptVersion: /* @__PURE__ */ __name((fileName) => { return String(this.fileVersions.get(fileName) || 1); }, "getScriptVersion"), getScriptSnapshot: /* @__PURE__ */ __name((fileName) => { const virtualContent = this.#virtualFiles.get(fileName); if (virtualContent) { return ts.ScriptSnapshot.fromString(virtualContent); } const content = ts.sys.readFile(fileName); return content ? ts.ScriptSnapshot.fromString(content) : void 0; }, "getScriptSnapshot"), getCurrentDirectory: /* @__PURE__ */ __name(() => this.options.projectDirectory, "getCurrentDirectory"), getCompilationSettings: /* @__PURE__ */ __name(() => parse