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,038 lines (1,031 loc) 38.3 kB
import { ProcessStats, TelemetryNodeClient, createTelemetryClient, pipeConsoleToTelemetryClient } from "../../chunk-MLK5YGGI.mjs"; import { TransportClientBase } from "../../chunk-HZQWVS5S.mjs"; import { canon } from "../../chunk-6CBODJTF.mjs"; import { RollingBuffer } from "../../chunk-LPKEDFLM.mjs"; import { LiveKitNodeClient, eouProviders, llmProviders, prepareAgentConfig, sttProviders, ttsProviders, vadProviders } from "../../chunk-XNMZQAOR.mjs"; import { pluginEventInputSchema, toMethodName } from "../../chunk-X455LF5V.mjs"; import { AsyncQueue, deepClone } from "../../chunk-D2T23PCX.mjs"; import { importServerBuild } from "../../chunk-CUJAJGIJ.mjs"; import { attempt, failure, isLifeError, lifeError, newId, success, toPublic } from "../../chunk-ZHBK6UTM.mjs"; import { __name } from "../../chunk-2D3UJWOA.mjs"; // server/agent-process/process.ts import { createBirpc } from "birpc"; // agent/server/class.ts import z2 from "zod"; // plugins/server/class.ts import z from "zod"; var PluginServer = class { static { __name(this, "PluginServer"); } def; agent; config; context; telemetry; queue = new AsyncQueue(); eventsListeners = /* @__PURE__ */ new Map(); contextListeners = /* @__PURE__ */ new Map(); streamHandlersQueues = []; externalInterceptHandlers = []; #internalAccessor; #waitProcessingResolvers = /* @__PURE__ */ new Map(); #waitResultResolvers = /* @__PURE__ */ new Map(); #waitProcessingHistory = new RollingBuffer(1e3); #waitResultHistory = new RollingBuffer(1e3); #handlersStates = /* @__PURE__ */ new Map(); constructor({ agent, definition, config, context = {} }) { this.def = definition; this.agent = agent; const { error: errConfig, data: parsedConfig } = definition.config.schema.safeParse(config); if (errConfig) throw lifeError({ code: "Validation", message: `Invalid config provided to plugin server '${definition.name}'.`, cause: errConfig }); this.config = parsedConfig; const { error: errContext, data: parsedContext } = definition.context.schema.safeParse(context); if (errContext) throw lifeError({ code: "Validation", message: `Invalid context provided to plugin server '${definition.name}'.`, cause: errContext }); this.context = parsedContext; this.telemetry = createTelemetryClient("plugin.server", { agentId: agent.id, agentSha: agent.sha, agentName: agent.def.name, agentConfig: agent.config, transportProviderName: agent.config.transport.provider, llmProviderName: agent.config.models.llm.provider, sttProviderName: agent.config.models.stt.provider, eouProviderName: agent.config.models.eou.provider, ttsProviderName: agent.config.models.tts.provider, vadProviderName: agent.config.models.vad.provider, pluginName: definition.name, pluginServerConfig: this.config }); this.#initClientRPC(); const [errAccessor, accessor] = this.getAccessor({ type: "server", name: this.def.name }); if (errAccessor) throw errAccessor; this.#internalAccessor = accessor; } /** * Produces an accessor object, exposing methods to interact safely with the plugin * from a given source (plugin, handler, client). * @param source - The source accessing the plugin. * @param handlerAccess - The access mode for the handler. * @returns An accessor object. */ getAccessor(source, handlerAccess) { const [errClone, clonedConfig] = attempt(() => deepClone(this.config)); if (errClone) return failure(errClone); const contextOnChange = /* @__PURE__ */ __name(((selector, callback) => { const id = newId("listener"); this.contextListeners.set(id, { id, callback, selector }); return success(() => this.contextListeners.delete(id)); }), "contextOnChange"); const contextGet = /* @__PURE__ */ __name((() => attempt(() => deepClone(this.context))), "contextGet"); const contextSet = /* @__PURE__ */ __name(((valueOrUpdater) => { const [errOld, oldContext] = attempt(() => deepClone(this.context)); if (errOld) return failure(errOld); if (typeof valueOrUpdater === "function") this.context = valueOrUpdater(oldContext); else this.context = valueOrUpdater; Promise.all([ // Notify context change listeners Array.from(this.contextListeners.values()).map(async (listener) => { try { const newSelectedValue = listener.selector(this.context); const oldSelectedValue = listener.selector(oldContext); const [errEqual, equal] = canon.equal(newSelectedValue, oldSelectedValue); if (errEqual) return failure(errEqual); if (equal) await listener.callback(deepClone(this.context), deepClone(oldContext)); } catch (error) { this.telemetry.log.error({ message: `Error while notifying context listeners in plugin '${this.def.name}'.`, error }); } }), // Send new context value updates via RPC this.agent.transport.call({ name: `plugin.${this.def.name}.context.changed`, schema: { input: z.object({ value: z.any(), timestamp: z.number() }) }, input: { value: this.context, timestamp: Date.now() } }) ]); return success(); }), "contextSet"); const eventsEmit = /* @__PURE__ */ __name(((event) => { const { error: errEvent, data: parsedEvent } = pluginEventInputSchema.safeParse(event); if (errEvent) return failure({ code: "Validation", message: "Invalid event shape for event.", cause: errEvent }); const isInternal = source.type === "server" && source.name === this.def.name; if (!isInternal && parsedEvent.name.startsWith("plugin.")) return failure({ code: "Validation", message: "Internal events cannot be emitted from outside the plugin." }); const eventDef = this.def.events?.find((e) => e.name === parsedEvent.name); if (!eventDef) return failure({ code: "Validation", message: `Event of type '${parsedEvent.name}' not found.` }); let parsedData = null; if (eventDef.dataSchema) { const { error: errData, data } = eventDef.dataSchema.safeParse(parsedEvent.data); parsedData = data; if (errData) return failure({ code: "Validation", message: `Invalid event data shape for '${parsedEvent.name}' event.`, cause: errData }); } const outputEvent = { id: newId("event"), name: parsedEvent.name, urgent: parsedEvent.urgent, data: parsedData, created: { at: Date.now(), by: source }, edited: false, dropped: false, contextChanges: [] }; if (outputEvent.urgent) this.queue.pushFirst(outputEvent); else this.queue.push(outputEvent); return success(outputEvent.id); }), "eventsEmit"); const eventsOn = /* @__PURE__ */ __name(((selector, callback, includeDropped = false) => { const id = newId("listener"); this.eventsListeners.set(id, { id, callback, selector, includeDropped }); return success(() => this.eventsListeners.delete(id)); }), "eventsOn"); const eventsOnce = /* @__PURE__ */ __name(((selector, callback, includeDropped = false) => { const [errOn, unsubscribe] = eventsOn( selector, async (event) => { unsubscribe?.(); await callback(event); }, includeDropped ); if (errOn) return failure(errOn); return success(unsubscribe); }), "eventsOnce"); const eventsWaitForProcessing = /* @__PURE__ */ __name((async (eventId) => { let resolver; const cleanupResolver = /* @__PURE__ */ __name(() => { if (!resolver) return; this.#waitProcessingResolvers.get(eventId)?.delete(resolver); if (this.#waitProcessingResolvers.get(eventId)?.size === 0) this.#waitProcessingResolvers.delete(eventId); }, "cleanupResolver"); try { if (this.#waitProcessingHistory.get().includes(eventId)) return success(); const resolverPromise = new Promise( (r) => resolver = /* @__PURE__ */ __name(() => void r(success()), "resolver") ); if (!resolver) throw new Error("Resolver not found. Shouldn't happen."); if (!this.#waitProcessingResolvers.has(eventId)) this.#waitProcessingResolvers.set(eventId, /* @__PURE__ */ new Set()); this.#waitProcessingResolvers.get(eventId)?.add(resolver); const timeoutPromise = new Promise((resolve) => { setTimeout(() => { resolve( failure({ code: "Timeout", message: `Waiting for event '${eventId}' processing timed out.`, isPublic: true }) ); }, 15e3); }); const [err] = await Promise.race([resolverPromise, timeoutPromise]); if (err) return failure(err); this.#waitProcessingHistory.add(eventId); return success(); } catch (error) { return failure({ code: "Unknown", cause: error }); } finally { cleanupResolver(); } }), "eventsWaitForProcessing"); const eventsWaitForResult = /* @__PURE__ */ __name((async (eventId, handlerName) => { let resolver; const cleanupResolver = /* @__PURE__ */ __name(() => { if (!resolver) return; this.#waitResultResolvers.get(eventId)?.get(handlerName)?.delete(resolver); if (this.#waitResultResolvers.get(eventId)?.get(handlerName)?.size === 0) this.#waitResultResolvers.get(eventId)?.delete(handlerName); if (this.#waitResultResolvers.get(eventId)?.size === 0) this.#waitResultResolvers.delete(eventId); }, "cleanupResolver"); try { const historyItem = this.#waitResultHistory.get().find((h) => h.eventId === eventId && h.handlerName === handlerName); if (historyItem) return historyItem.result; const resolverPromise = new Promise((r) => resolver = r); if (!resolver) throw new Error("Resolver not found. Shouldn't happen."); if (!this.#waitResultResolvers.has(eventId)) this.#waitResultResolvers.set(eventId, /* @__PURE__ */ new Map()); if (!this.#waitResultResolvers.get(eventId)?.get(handlerName)) this.#waitResultResolvers.get(eventId)?.set(handlerName, /* @__PURE__ */ new Set()); this.#waitResultResolvers.get(eventId)?.get(handlerName)?.add(resolver); const timeoutPromise = new Promise((resolve) => { setTimeout(() => { resolve( failure({ code: "Timeout", message: `Waiting for event '${eventId}' result timed out.`, isPublic: true }) ); }, 15e3); }); const result = await Promise.race([resolverPromise, timeoutPromise]); this.#waitResultHistory.add({ eventId, handlerName, result }); return result; } catch (error) { return failure({ code: "Unknown", cause: error }); } finally { cleanupResolver(); } }), "eventsWaitForResult"); if (source.type === "handler") { return success({ config: clonedConfig, context: { get: contextGet, ...handlerAccess === "write" ? { set: contextSet } : {}, onChange: contextOnChange }, events: { emit: eventsEmit, waitForResult: eventsWaitForResult, waitForProcessing: eventsWaitForProcessing } }); } if (source.type === "server") { return success({ config: clonedConfig, context: { get: contextGet, onChange: contextOnChange }, events: { emit: eventsEmit, on: eventsOn, once: eventsOnce, waitForResult: eventsWaitForResult, waitForProcessing: eventsWaitForProcessing } }); } if (source.type === "client") { return success({ config: clonedConfig, context: { get: contextGet, onChange: contextOnChange }, events: { emit: eventsEmit, on: eventsOn, once: eventsOnce, waitForResult: eventsWaitForResult, waitForProcessing: eventsWaitForProcessing } }); } return failure({ code: "NotFound", message: `Unknown source type '${source.type}'.` }); } /** * Produces a map of dependencies' accessors, see `getAccessor()`. * @param source - The source accessing the dependencies. * @returns A map of dependencies' accessors. */ getDependenciesAccessors(source) { const dependenciesAccessors = {}; for (const dependencyDef of this.def.dependencies ?? []) { const depServer = this.agent.plugins?.[toMethodName(dependencyDef.name)]; if (!depServer) return failure({ code: "NotFound", message: `Failed to obtain server instance for dependency plugin '${dependencyDef.name}'. Shouldn't happen.` }); const [errAccessor, accessor] = depServer.getAccessor(source); if (errAccessor) return failure(errAccessor); dependenciesAccessors[dependencyDef.name] = accessor; } return success(dependenciesAccessors); } async start() { return await this.telemetry.trace(`plugin.${this.def.name}.start()`, async () => { const streamHandlers = this.def.handlers.filter((h) => h.mode === "stream"); for (const handler of streamHandlers) { const queue = new AsyncQueue(); this.streamHandlersQueues.push(queue); (async () => { for await (const event of queue) await this.#executeHandler(handler, event); })(); } const interceptHandlers = this.def.handlers.filter((h) => h.mode === "intercept"); for (const handler of interceptHandlers) { for (const dependencyDef of this.def.dependencies ?? []) { const dependency = this.agent.plugins?.[dependencyDef.name]; if (!dependency) { this.telemetry.log.error( lifeError({ code: "NotFound", message: `Failed to obtain server instance for dependency plugin '${dependencyDef.name}'. Shouldn't happen.` }) ); continue; } dependency.externalInterceptHandlers.push({ dependent: this, handler }); } } (async () => { for await (const event of this.queue) { try { for (const { handler, dependent } of this.externalInterceptHandlers) { await this.#executeHandler(handler, event, dependent, (result) => { if (result.type === "drop") { if (event.dropped) event.dropped = { at: Date.now(), by: { plugin: this.def.name, handler: handler.name }, reason: result.reason }; } else if (result.type === "next") { const [errClone, oldData] = attempt(() => deepClone(event.data)); if (errClone) return failure(errClone); if (!event.edited) event.edited = []; event.edited.push({ at: Date.now(), by: { plugin: this.def.name, handler: handler.name }, reason: result.reason, dataBefore: oldData, dataAfter: result.data }); event.data = result.data; } }); if (event.dropped) break; } if (!event.dropped) { const blockHandlers = this.def.handlers.filter((h) => h.mode === "block"); for (const handler of blockHandlers) { const [errOld, oldContext] = attempt(() => deepClone(this.context)); if (errOld) { this.telemetry.log.error({ error: errOld }); continue; } await this.#executeHandler(handler, event); const [errEqual, equal] = canon.equal( this.context, oldContext ); if (errEqual) { this.telemetry.log.error({ error: errEqual }); continue; } if (!equal) { if (!event.contextChanges) event.contextChanges = []; event.contextChanges.push({ at: Date.now(), byHandler: handler.name, valueBefore: oldContext, valueAfter: this.context }); } } for (const queue of this.streamHandlersQueues) queue.push(event); } await Promise.all( Array.from(this.eventsListeners.values()).map( async ({ id, callback, selector, includeDropped }) => { if (this.#eventMatchesSelector(event, selector)) { if (!event.dropped || includeDropped) { if (callback === "remote") { const [err] = await this.agent.transport.call({ name: `plugin.${this.def.name}.events.callback`, schema: { input: z.object({ listenerId: z.string(), event: pluginEventInputSchema }) }, input: { listenerId: id, event } }); if (err) this.telemetry.log.error({ message: `Error while steaming event to remote listener in plugin '${this.def.name}'.`, error: err }); } else await callback(event); } } } ) ); const waiters = this.#waitProcessingResolvers.get(event.id) ?? []; for (const resolver of waiters) resolver(); } catch (error) { this.telemetry.log.error({ message: `Unknown error while executing event in plugin '${this.def.name}'.`, error }); } } })(); const [errEmit, eventId] = this.#emitInternalEvent({ name: "plugin.start", data: { isRestart: this.agent.isRestart, restartCount: 0 } }); if (errEmit) return failure(errEmit); const [errWaitForResult] = await this.#internalAccessor.events.waitForProcessing(eventId); if (errWaitForResult) return failure(errWaitForResult); return success(); }); } async stop() { return await this.telemetry.trace(`plugin.${this.def.name}.stop()`, async () => { const [errEmit, eventId] = this.#emitInternalEvent({ name: "plugin.stop", urgent: true }); if (errEmit) return failure(errEmit); const [errWaitForResult] = await this.#internalAccessor.events.waitForProcessing(eventId); if (errWaitForResult) return failure(errWaitForResult); this.queue.stop(); for (const queue of this.streamHandlersQueues) queue.stop(); this.#handlersStates.clear(); return success(); }); } #getHandlerState(handler, accessor) { const savedState = this.#handlersStates.get(handler.name); if (savedState) return savedState; const initialState = typeof handler.state === "function" ? handler.state({ config: accessor.config }) : handler.state ?? {}; this.#handlersStates.set(handler.name, initialState); return initialState; } #emitInternalEvent(event) { const [errEmit, eventId] = this.#internalAccessor.events.emit(event); if (errEmit) return failure(errEmit); return success(eventId); } async #executeHandler(handler, event, dependency, onInterceptResult) { const result = await (async () => { const [errClone, clonedEvent] = attempt(() => deepClone(event)); if (errClone) return failure(errClone); const handlerAccess = handler.mode === "block" ? "write" : "read"; const [errAccessor, accessor] = this.getAccessor( { type: "handler", plugin: this.def.name, handler: handler.name, event: event.name }, handlerAccess ); if (errAccessor) return failure(errAccessor); const state = this.#getHandlerState(handler, accessor); const [errDependency, dependencyAccessor] = dependency ? dependency.getAccessor({ type: "server", name: dependency.def.name }) : success({}); if (errDependency) return failure(errDependency); const [errDependencies, dependenciesAccessor] = this.getDependenciesAccessors({ type: "handler", plugin: this.def.name, handler: handler.name, event: event.name }); if (errDependencies) return failure(errDependencies); const drop = /* @__PURE__ */ __name((reason) => onInterceptResult?.({ type: "drop", reason }), "drop"); const next = /* @__PURE__ */ __name((data, reason) => onInterceptResult?.({ type: "next", data, reason }), "next"); return await this.telemetry.trace( `plugin.${this.def.name}.handler.${handler.name}`, async (span) => attempt( async () => await handler.onEvent({ event: clonedEvent, plugin: toPublic(accessor), state, telemetry: span, agent: toPublic(this.agent), ...handler.mode === "intercept" ? { drop, next, dependency: dependencyAccessor } : { dependencies: dependenciesAccessor } }) ) ); })(); if (result[0]) { this.telemetry.log.error({ message: `Error while executing ${handler.mode} handler '${handler.name}' in plugin '${this.def.name}'.`, error: result[0] }); this.#emitInternalEvent({ name: "plugin.error", data: { error: result[0], event } }); } const waiters = this.#waitResultResolvers.get(event.id)?.get(handler.name) ?? []; for (const resolver of waiters) resolver(result); } #initClientRPC() { const [errAccessor, accessor] = this.getAccessor({ type: "client", name: this.def.name }); if (errAccessor) return failure(errAccessor); this.agent.transport.register({ name: `plugin.${this.def.name}.context.get`, schema: { output: z.object().loose() }, execute: /* @__PURE__ */ __name(() => success({ value: this.context, timestamp: Date.now() }), "execute") }); this.agent.transport.register({ name: `plugin.${this.def.name}.events.emit`, schema: { input: pluginEventInputSchema, output: z.object({ id: z.string() }) }, execute: /* @__PURE__ */ __name(async (input) => { const [err, id] = await accessor.events.emit(input); if (err) return failure(err); return success({ id }); }, "execute") }); this.agent.transport.register({ name: `plugin.${this.def.name}.events.subscribe`, schema: { input: z.object({ listenerId: z.string(), selector: z.any(), includeDropped: z.boolean().prefault(false) }) }, execute: /* @__PURE__ */ __name((input) => { const { listenerId, selector, includeDropped } = input; this.eventsListeners.set(listenerId, { id: listenerId, callback: "remote", selector, includeDropped }); return success(); }, "execute") }); this.agent.transport.register({ name: `plugin.${this.def.name}.events.unsubscribe`, schema: { input: z.object({ listenerId: z.string() }) }, execute: /* @__PURE__ */ __name((input) => { const { listenerId } = input; this.eventsListeners.delete(listenerId); return success(); }, "execute") }); this.agent.transport.register({ name: `plugin.${this.def.name}.events.waitForProcessing`, schema: { input: z.object({ eventId: z.string() }) }, execute: /* @__PURE__ */ __name(async (input) => await accessor.events.waitForProcessing(input.eventId), "execute") }); this.agent.transport.register({ name: `plugin.${this.def.name}.events.waitForResult`, schema: { input: z.object({ eventId: z.string(), handlerName: z.string() }), output: z.object().loose() }, execute: /* @__PURE__ */ __name(async (input) => await accessor.events.waitForResult( input.eventId, input.handlerName ), "execute") }); } #eventMatchesSelector(event, selector) { const isAllSelector = selector === "*"; const isArraySelectorAndIncludesEvent = Array.isArray(selector) && selector.includes(event.name); const isObjectSelectorAndIncludesEvent = typeof selector === "object" && "include" in selector && (selector.include === "*" || selector.include.includes(event.name)); const isObjectSelectorAndExcludesEvent = typeof selector === "object" && "exclude" in selector && selector.exclude?.includes(event.name); return isAllSelector || isArraySelectorAndIncludesEvent || isObjectSelectorAndIncludesEvent && !isObjectSelectorAndExcludesEvent; } }; // shared/ensure-server.ts function ensureServer(featureName) { const isBrowser = typeof window !== "undefined" && typeof window.document !== "undefined"; if (isBrowser) { throw new Error(`\u274C "${featureName}" is a server-only and must not run in the browser.`); } } __name(ensureServer, "ensureServer"); // transport/client/node.ts ensureServer("transport.client.node"); var nodeTransportProviders = { livekit: LiveKitNodeClient }; var TransportNodeClient = class extends TransportClientBase { static { __name(this, "TransportNodeClient"); } constructor({ config, obfuscateErrors = false, telemetry: telemetry2 = null }) { const ProviderClass = nodeTransportProviders[config.provider]; super({ provider: new ProviderClass(config), obfuscateErrors, telemetry: telemetry2 }); } }; // agent/server/class.ts var AgentServer = class { static { __name(this, "AgentServer"); } def; id; sha; config; transport; storage = null; models; plugins = {}; scope; isRestart; telemetry; #initialPluginsContexts; constructor({ id, sha, definition, scope, config, pluginsContexts, isRestart }) { this.id = id; this.sha = sha; this.def = definition; this.scope = scope ?? this.def.scope.schema.parse({}); this.config = config; this.isRestart = isRestart ?? false; this.#initialPluginsContexts = pluginsContexts ?? {}; this.telemetry = createTelemetryClient("agent.server", { agentId: id, agentSha: this.sha, agentName: this.def.name, agentConfig: this.config, transportProviderName: this.config.transport.provider, llmProviderName: this.config.models.llm.provider, sttProviderName: this.config.models.stt.provider, eouProviderName: this.config.models.eou.provider, ttsProviderName: this.config.models.tts.provider, vadProviderName: this.config.models.vad.provider }); this.transport = new TransportNodeClient({ config: this.config.transport, obfuscateErrors: true, telemetry: this.telemetry }); const vadProvider = vadProviders[this.config.models.vad.provider]; const sttProvider = sttProviders[this.config.models.stt.provider]; const eouProvider = eouProviders[this.config.models.eou.provider]; const llmProvider = llmProviders[this.config.models.llm.provider]; const ttsProvider = ttsProviders[this.config.models.tts.provider]; this.models = { vad: new vadProvider.class(this.config.models.vad), stt: new sttProvider.class(this.config.models.stt), eou: new eouProvider.class(this.config.models.eou), llm: new llmProvider.class(this.config.models.llm), tts: new ttsProvider.class(this.config.models.tts) }; this.#initializePlugins(); } #initializePlugins() { const pluginNames = this.def.plugins.map((p) => p.name); const duplicates = pluginNames.filter((name, index) => pluginNames.indexOf(name) !== index); if (duplicates.length > 0) { const uniqueDuplicates = [...new Set(duplicates)]; throw lifeError({ code: "Validation", message: `Two or more plugins are named "${uniqueDuplicates.join('", "')}". Plugin names must be unique. (agent: '${this.def.name}')` }); } for (const plugin of this.def.plugins) { for (const dependency of plugin.dependencies) { const depPlugin = this.def.plugins.find((p) => p.name === dependency.name); if (!depPlugin) { throw lifeError({ code: "Validation", message: `Plugin '${plugin.name}' depends on plugin '${dependency.name}', but '${dependency.name}' is not registered. (agent: '${this.def.name}')` }); } } } this.transport.register({ name: "agent.has-plugin-server", schema: { input: z2.object({ pluginName: z2.string() }), output: z2.object({ hasServer: z2.boolean() }) }, execute: /* @__PURE__ */ __name(({ pluginName }) => { const plugin = this.plugins?.[pluginName]; if (!plugin) return success({ hasServer: false }); return success({ hasServer: true }); }, "execute") }); } async start() { return await this.telemetry.trace("AgentServer.start()", async () => { try { for (const definition of this.def.plugins) { const { error: errorContext, data: parsedContext } = definition.context.schema.safeParse( this.#initialPluginsContexts?.[definition.name] ?? {} ); if (errorContext) { return failure({ code: "Validation", message: `Invalid initial context value for plugin '${definition.name}'.`, cause: errorContext }); } const { error: errorConfig, data: parsedConfig } = definition.config.schema.safeParse( this.def.pluginConfigs?.[definition.name] ?? {} ); if (errorConfig) { return failure({ code: "Validation", message: `Invalid config value for plugin '${definition.name}'.`, cause: errorConfig }); } const [errCreate, instance] = attempt( () => new PluginServer({ agent: this, definition, config: parsedConfig, context: parsedContext }) ); if (errCreate) return failure(errCreate); this.plugins[definition.name] = instance; } const result = await Promise.all(Object.values(this.plugins).map((p) => p.start())); const err = result.find((r) => r[0])?.[0]; if (err) return failure(err); return success(); } catch (error) { return failure({ code: "Unknown", cause: error }); } }); } async stop() { return await this.telemetry.trace("AgentServer.stop()", async (span) => { try { await Promise.all( Object.entries(this.plugins).map( ([pluginId, plugin]) => plugin.stop().catch( (error) => span.log.error({ message: `Error stopping plugin ${pluginId}:`, error }) ) ) ); const [errLeave] = await this.transport.leaveRoom(); if (errLeave) return failure(errLeave); return success(); } catch (error) { return failure({ code: "Unknown", cause: error }); } }); } }; // server/agent-process/process.ts var processStats = new ProcessStats(); var agentServer = null; var telemetry = null; var rpc = createBirpc( { init(params) { telemetry = createTelemetryClient("agent.process", { agentId: params.agentId }); pipeConsoleToTelemetryClient(telemetry); return success(); }, // async start(params) { try { const [errIndex, buildIndex] = await importServerBuild({ projectDirectory: process.cwd(), noCache: true }); if (errIndex) return failure(errIndex); const build = buildIndex?.[params.name]; if (!build) return failure({ code: "NotFound", message: `Agent '${params.name}' not found.` }); const [errConfig, config] = prepareAgentConfig( build.definition.config, build.globalConfigs ); if (errConfig) return failure(errConfig); const [errCreate, instance] = attempt( () => new AgentServer({ id: params.id, definition: build.definition, scope: params.scope, sha: build.sha, config: config.server, pluginsContexts: params.pluginsContexts, isRestart: params.isRestart }) ); if (errCreate) return failure(errCreate); agentServer = instance; const [err] = await agentServer.start(); if (err) return failure(err); for (const pluginDef of build.definition.plugins) { const [errAccessor, accessor] = agentServer.plugins[pluginDef.name]?.getAccessor({ type: "server", name: pluginDef.name }) ?? failure({ code: "NotFound", message: `Plugin '${pluginDef.name}' not found.` }); if (errAccessor) return failure(errAccessor); accessor.context.onChange( (c) => c, async (c) => { if (!agentServer) return; const [error] = await rpc.syncContext({ agentId: agentServer.id, pluginName: pluginDef.name, context: c, timestamp: Date.now() }); if (error) telemetry?.log.error({ message: `Failed to sync for plugin '${pluginDef.name}' in agent '${agentServer.def.name}' process.`, error }); } ); } await agentServer.transport.joinRoom(params.transportRoom.name, params.transportRoom.token); await rpc.ready(); telemetry?.log.info({ message: "Agent started successfully." }); return success(); } catch (error) { return failure({ code: "Unknown", cause: error }); } }, async stop() { try { if (agentServer) { const [err] = await agentServer.stop(); if (err) return failure(err); agentServer = null; } return success(); } catch (error) { return failure({ code: "Unknown", cause: error }); } }, // Simple ping to check if process is responsive async ping() { return await success(); }, // Get detailed stats from the child process async getProcessStats() { try { return await processStats.get(); } catch (error) { return failure({ code: "Unknown", cause: error }); } } }, { post: /* @__PURE__ */ __name((data) => process.send?.(data), "post"), on: /* @__PURE__ */ __name((fn) => process.on("message", fn), "on"), serialize: /* @__PURE__ */ __name((data) => { const [error, result] = canon.serialize(data); if (error) { throw lifeError({ code: "Validation", message: "Failed to serialize data from agent process to server. The message has been discarded.", attributes: { agentId: agentServer?.id, agentName: agentServer?.def.name, data }, cause: error }); } return result; }, "serialize"), deserialize: /* @__PURE__ */ __name((data) => { const [error, result] = canon.deserialize(data); if (error) { throw lifeError({ code: "Validation", message: "Failed to deserialize data from agent process to server. The message has been discarded.", attributes: { agentId: agentServer?.id, agentName: agentServer?.def.name, data }, cause: error }); } return result; }, "deserialize"), onFunctionError: /* @__PURE__ */ __name((error) => { telemetry?.log.error( isLifeError(error) ? error : lifeError({ code: "Unknown", cause: error }) ); }, "onFunctionError"), onGeneralError: /* @__PURE__ */ __name((error) => { telemetry?.log.error( isLifeError(error) ? error : lifeError({ code: "Unknown", cause: error }) ); }, "onGeneralError"), // Disable Birpc timeout onTimeoutError: /* @__PURE__ */ __name(() => true, "onTimeoutError"), timeout: -1 } ); TelemetryNodeClient.registerGlobalConsumer({ start: /* @__PURE__ */ __name(async (queue) => { for await (const signal of queue) rpc.syncTelemetry(signal); }, "start") }); process.on("uncaughtException", async (error) => { telemetry?.log.error({ error }); await telemetry?.flushConsumers(1e3); process.exit(1); }); process.on("unhandledRejection", async (reason) => { telemetry?.log.error({ message: reason instanceof Error ? reason.message : String(reason), error: reason instanceof Error ? reason : void 0 }); await telemetry?.flushConsumers(1e3); process.exit(1); }); //# sourceMappingURL=process.mjs.map