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 lines • 133 kB
Source Map (JSON)
{"version":3,"sources":["../../agent/server/builder.ts","../../plugins/defaults/memories/server/builder.ts","../../plugins/defaults/stores/builder.ts","../../plugins/server/builder.ts","../../shared/messages.ts","../../plugins/defaults/generation/server/config.ts","../../plugins/defaults/generation/server/context.ts","../../plugins/defaults/generation/server/status.ts","../../plugins/defaults/generation/server/events.ts","../../models/llm/resources.ts","../../plugins/defaults/generation/server/orchestrator.ts","../../plugins/defaults/generation/server/generation.ts","../../plugins/defaults/generation/server/index.ts","../../plugins/defaults/memories/server/config.ts","../../plugins/defaults/memories/server/context.ts","../../plugins/defaults/memories/server/events.ts","../../plugins/defaults/memories/server/index.ts","../../plugins/defaults/stores/server.ts","../../exports/server.ts"],"sourcesContent":["import { z } from \"zod\";\nimport type { agentServerConfig } from \"@/agent/server/config\";\nimport { defaults } from \"@/exports/server\";\nimport type { PluginConfig, PluginDefinition } from \"@/plugins/server/types\";\nimport { toMethodName } from \"@/shared/method-name\";\nimport type { Override } from \"@/shared/types\";\nimport type { AgentDefinition, AgentScopeDefinition, TypedAgentBuilder } from \"./types\";\n\nexport class AgentBuilder<const AgentDef extends AgentDefinition, Excluded extends string = never> {\n readonly def: AgentDef;\n\n constructor(definition: AgentDef) {\n this.def = definition;\n }\n\n config(config: z.input<typeof agentServerConfig.schema>) {\n const builder = new AgentBuilder({ ...this.def, config });\n const builderWithPlugins = AgentBuilder.withPluginsMethods(builder);\n return builderWithPlugins as unknown as TypedAgentBuilder<AgentDef, Excluded | \"config\">;\n }\n\n scope<Schema extends z.ZodObject>(scope: AgentScopeDefinition<Schema>) {\n const builder = new AgentBuilder({ ...this.def, scope });\n const builderWithPlugins = AgentBuilder.withPluginsMethods(builder) as never;\n return builderWithPlugins as TypedAgentBuilder<\n Override<AgentDef, \"scope\", AgentScopeDefinition<Schema>>,\n Excluded | \"scope\"\n >;\n }\n\n /**\n * Register plugins to extend the agent server features.\n * Defaults to `[generation, memories, stores, actions, percepts]` plugins if not specified.\n *\n * In case you want to register custom plugins and still keep the defaults you can do:\n * ```ts\n * import { defaults } from \"life/client\";\n *\n * defineAgentClient(\"my-agent\").plugins([...defaults.plugins, myCustomPlugin]);\n * ```\n *\n * Or if you want only some of the defaults, you can do:\n * ```ts\n * import { defaults } from \"life/client\";\n *\n * defineAgentClient(\"my-agent\").plugins([defaults.plugins.generation, defaults.plugins.memories]);\n * ```\n */\n plugins<Plugins extends { def: PluginDefinition }[]>(plugins: Plugins) {\n const builder = new AgentBuilder({ ...this.def, plugins: plugins.map((p) => p.def) });\n const builderWithPlugins = AgentBuilder.withPluginsMethods(builder) as never;\n return builderWithPlugins as TypedAgentBuilder<\n Override<\n Override<\n AgentDef,\n \"plugins\",\n {\n [K in keyof Plugins]: Plugins[K][\"def\"];\n }\n >,\n \"pluginConfigs\",\n {\n [K in Plugins[number] as K[\"def\"][\"name\"]]: PluginConfig<K[\"def\"][\"config\"], \"output\">;\n }\n >,\n Excluded | \"plugins\"\n >;\n }\n\n // biome-ignore lint/suspicious/noExplicitAny: reason\n static withPluginsMethods<Builder extends AgentBuilder<any, any>>(builder: Builder) {\n for (const plugin of builder.def.plugins) {\n Object.assign(builder, {\n [toMethodName(plugin.name)]: (\n config: z.input<PluginDefinition[\"config\"][\"schema\"]>,\n ): unknown => {\n const newBuilder = new AgentBuilder({\n ...builder.def,\n pluginConfigs: {\n ...(builder.def.pluginConfigs ?? {}),\n [plugin.name]: config,\n },\n });\n return AgentBuilder.withPluginsMethods(newBuilder);\n },\n });\n }\n return builder;\n }\n}\n\nexport function defineAgent<const Name extends string>(name: Name) {\n const defaultDefinition = {\n name,\n config: {},\n scope: { schema: z.object(), hasAccess: () => true },\n plugins: [...defaults.plugins].map((p) => p.def),\n pluginConfigs: {},\n } as const satisfies AgentDefinition;\n const builder = new AgentBuilder(defaultDefinition);\n const builderWithPlugins = AgentBuilder.withPluginsMethods(builder);\n return builderWithPlugins as TypedAgentBuilder<typeof builderWithPlugins.def, never>;\n}\n","import { z } from \"zod\";\nimport type { Message } from \"@/shared/messages\";\n\n// Dependencies\ninterface _MemoryDependenciesDefinition {\n stores: { name: string }[];\n collections: { name: string }[];\n}\n\ntype MemoryDependenciesDefinition =\n | _MemoryDependenciesDefinition\n | { _definition: _MemoryDependenciesDefinition };\n\n// Config\nexport const memoryConfigSchema = z.object({\n behavior: z.enum([\"blocking\", \"non-blocking\"]).prefault(\"blocking\"),\n});\n\nexport type MemoryConfig<T extends \"input\" | \"output\"> = T extends \"input\"\n ? z.input<typeof memoryConfigSchema>\n : z.output<typeof memoryConfigSchema>;\n\n// Definition\nexport interface MemoryDefinition {\n name: string;\n config: MemoryConfig<\"output\">;\n output?: Message[] | ((params: { messages: Message[] }) => Message[] | Promise<Message[]>);\n onHistoryChange?: (params: { messages: Message[] }) => void;\n dependencies: MemoryDependenciesDefinition;\n}\n\n// Builder\nexport class MemoryDefinitionBuilder<\n const Definition extends MemoryDefinition,\n Excluded extends string = never,\n> {\n _definition: Definition;\n\n constructor(def: Definition) {\n this._definition = def;\n }\n\n dependencies<Dependencies extends MemoryDependenciesDefinition>(dependencies: Dependencies) {\n type NewExcluded = Excluded | \"dependencies\";\n return new MemoryDefinitionBuilder({\n ...this._definition,\n dependencies: \"_definition\" in dependencies ? dependencies._definition : dependencies,\n }) as Omit<\n MemoryDefinitionBuilder<Definition & { dependencies: Dependencies }, NewExcluded>,\n NewExcluded\n >;\n }\n\n config(config: MemoryConfig<\"input\">) {\n const parsedConfig = memoryConfigSchema.parse(config);\n type NewExcluded = Excluded | \"config\";\n return new MemoryDefinitionBuilder({\n ...this._definition,\n config: parsedConfig,\n }) as Omit<\n MemoryDefinitionBuilder<Definition & { config: typeof parsedConfig }, NewExcluded>,\n NewExcluded\n >;\n }\n\n output(\n // biome-ignore lint/nursery/noShadow: expected here\n params: Message[] | ((params: { messages: Message[] }) => Message[] | Promise<Message[]>),\n ) {\n type NewExcluded = Excluded | \"output\";\n return new MemoryDefinitionBuilder({\n ...this._definition,\n output: params,\n }) as Omit<\n MemoryDefinitionBuilder<Definition & { output: typeof params }, NewExcluded>,\n NewExcluded\n >;\n }\n // biome-ignore lint/nursery/noShadow: expected here\n onHistoryChange(params: (params: { messages: Message[] }) => void) {\n type NewExcluded = Excluded | \"onHistoryChange\";\n return new MemoryDefinitionBuilder({\n ...this._definition,\n onHistoryChange: params,\n }) as Omit<\n MemoryDefinitionBuilder<Definition & { onHistoryChange: typeof params }, NewExcluded>,\n NewExcluded\n >;\n }\n}\n\nexport function defineMemory<const Name extends string>(name: Name) {\n return new MemoryDefinitionBuilder({\n name,\n config: memoryConfigSchema.parse({}), // Will default to { behavior: \"blocking\" }\n dependencies: {\n stores: [],\n collections: [],\n },\n });\n}\n","import { z } from \"zod\";\nimport type { serializableValueSchema } from \"@/shared/canon\";\n\n// - Config\nconst commonStoreConfigSchema = z.object({\n schema: z.custom<\n | z.ZodRecord<z.ZodString, typeof serializableValueSchema>\n | z.ZodArray<typeof serializableValueSchema>\n >((val) => {\n if (val instanceof z.ZodArray || val instanceof z.ZodRecord) return true;\n return false;\n }),\n});\n\nexport const controlledStoreConfigSchema = commonStoreConfigSchema.extend({\n type: z.literal(\"controlled\"),\n\n ttl: z.number().optional(),\n});\n\nexport const freeformStoreConfigSchema = commonStoreConfigSchema.extend({\n type: z.literal(\"freeform\"),\n});\n\nexport const storeConfigSchema = z.union([controlledStoreConfigSchema, freeformStoreConfigSchema]);\n\nexport type StoreConfig<T extends \"input\" | \"output\"> = T extends \"input\"\n ? z.input<typeof storeConfigSchema>\n : z.output<typeof storeConfigSchema>;\n\n// - Retrieve\nexport type StoreRetrieve<Config extends StoreConfig<\"output\">> = () =>\n | z.infer<Config[\"schema\"]>\n | Promise<z.infer<Config[\"schema\"]>>;\n\n// - Definition\nexport interface StoreDefinition {\n name: string;\n config: StoreConfig<\"output\">;\n retrieve?: () => unknown | Promise<unknown>;\n}\n\n// Builder class\nexport class StoreDefinitionBuilder<\n const Definition extends StoreDefinition,\n Excluded extends string = never,\n> {\n _definition: Definition;\n\n constructor(def: Definition) {\n this._definition = def;\n }\n\n config<Config extends StoreConfig<\"input\">>(config: Config) {\n const parsedConfig = storeConfigSchema.parse(config);\n type NewExcluded =\n | Excluded\n | \"config\"\n | (Config[\"type\"] extends \"controlled\" ? never : \"retrieve\");\n return new StoreDefinitionBuilder({\n ...this._definition,\n config: parsedConfig,\n }) as Omit<StoreDefinitionBuilder<Definition & { config: Config }, NewExcluded>, NewExcluded>;\n }\n\n retrieve(retrieve: StoreRetrieve<Definition[\"config\"]>) {\n type NewExcluded = Excluded | \"retrieve\";\n return new StoreDefinitionBuilder({\n ...this._definition,\n retrieve,\n }) as Omit<\n StoreDefinitionBuilder<\n Definition & { retrieve: StoreRetrieve<Definition[\"config\"]> },\n NewExcluded\n >,\n NewExcluded\n >;\n }\n}\n\nexport function defineStore<const Name extends string>(name: Name) {\n return new StoreDefinitionBuilder({ name, config: storeConfigSchema.parse({}) });\n}\n\n// const store = defineStore(\"store\")\n// .config({\n// type: \"controlled\",\n// schema: z.object({\n// name: z.string(),\n// }),\n// ttl: 1000,\n// })\n// .retrieve(() => {\n// return { name: \"test\" };\n// });\n","import { z } from \"zod\";\nimport type { Override, Without } from \"@/shared/types\";\nimport { type ZodObjectWithTelemetry, zodObjectWithTelemetry } from \"@/telemetry/helpers/zod\";\nimport {\n type FilterHandlersByName,\n internalEventsDef,\n type PluginConfigDefinition,\n type PluginContextDefinition,\n type PluginDefinition,\n type PluginEventsDefinition,\n type PluginHandlerDefinition,\n type PluginHandlerStateDefinition,\n type TypedPluginBuilder,\n} from \"./types\";\n\n/**\n * ### `definePluginConfig()`\n *\n * Standalone version of `definePlugin().config()` used to break down plugin definition.\n *\n * Its output must then be passed to `definePlugin().$config()`.\n *\n * @example\n * ```ts\n * const configDef = definePluginConfig({ schema: z.object({ name: z.string() }) });\n * const plugin = definePlugin(\"my-plugin\").$config(configDef); // <-- Here\n *\n * // Equivalent to:\n * const plugin = definePlugin(\"my-plugin\").config({ schema: z.object({ name: z.string() }) }); // <-- Here\n * ```\n * \n *\n * ---\n * @returns The config definition.\n */\nexport const definePluginConfig = <Schema extends z.ZodObject>(\n definition: ZodObjectWithTelemetry<Schema, \"input\">,\n) => zodObjectWithTelemetry(definition);\n\n/**\n * ### `definePluginContext()`\n *\n * Standalone version of `definePlugin().context()` used to break down plugin definition.\n *\n * Its output must then be passed to `definePlugin().$context()`.\n *\n * @example\n * ```ts\n * const contextDef = definePluginContext(z.object({ name: z.string() }));\n * const plugin = definePlugin(\"my-plugin\").$context(contextDef);\n *\n * // Equivalent to:\n * const plugin = definePlugin(\"my-plugin\").context(z.object({ name: z.string() }));\n * ```\n * \n *\n * ---\n * @returns\n */\nexport const definePluginContext = <Schema extends z.ZodObject>(\n definition: ZodObjectWithTelemetry<Schema, \"input\">,\n) => zodObjectWithTelemetry(definition);\n\n/**\n * ### `definePluginEvents()`\n *\n * Standalone version of `definePlugin().events()` used to break down plugin definition.\n *\n * Its output must then be passed to `definePlugin().$events()`.\n *\n * @example\n * ```ts\n * const eventsDef = definePluginEvents([{ name: \"my-event\" }]);\n * const plugin = definePlugin(\"my-plugin\").$events(eventsDef);\n *\n * // Equivalent to:\n * const plugin = definePlugin(\"my-plugin\").events([{ name: \"my-event\" }]);\n * ```\n * \n *\n * ---\n * @returns The events definition.\n */\nexport const definePluginEvents = <\n const Names extends string[],\n EventsDef extends PluginEventsDefinition<Names>,\n>(\n ...events: EventsDef\n) => [...internalEventsDef, ...events] as unknown as typeof internalEventsDef | EventsDef;\n\n/**\n * Builder class for the plugin definition.\n * Do not use directly, use `definePlugin()` instead.\n */\nexport class PluginBuilder<PluginDef extends PluginDefinition, Excluded extends string = never> {\n readonly def: PluginDef;\n\n constructor(definition: PluginDef) {\n this.def = definition;\n }\n\n /**\n * ### `.dependencies()`\n *\n * Specify other plugins as required by this plugin.\n *\n * Their context, events, and config can then be accessed from `dependencies.*` inside handlers.\n *\n * @see TODO: Add docs link\n *\n * @example\n * ```ts\n * const plugin = definePlugin(\"my-plugin\")\n * .dependencies([anotherPlugin]);\n * .addHandler({\n * name: \"handler-1\",\n * mode: \"block\",\n * onEvent: ({ dependencies }) => {\n * if (dependencies.anotherPlugin.config.delayMs > 10) { // <-- Here\n * // ...\n * }\n * },\n * });\n * ```\n * \n *\n * ---\n * @param plugins - The dependencies definitions.\n * @returns TypedPluginBuilder\n */\n dependencies<Plugins extends { def: PluginDefinition }[]>(plugins: Plugins) {\n const dependencies = plugins.map((p) => p.def);\n const builder = new PluginBuilder({ ...this.def, dependencies });\n return builder as TypedPluginBuilder<\n Override<\n PluginDef,\n \"dependencies\",\n {\n [K in keyof Plugins]: Without<Plugins[K][\"def\"], \"dependencies\">;\n }\n >,\n Excluded | \"dependencies\"\n >;\n }\n\n /**\n * ### `.config()`\n *\n * Add a configuration that users can provide to tweak plugin's behavior.\n *\n * @see TODO: Add docs link\n *\n * @example\n * ```ts\n * const myPlugin = definePlugin(\"my-plugin\")\n * .config({ schema: z.object({ enableVoice: z.boolean() }) });\n *\n * const myAgent = defineAgent(\"my-agent\")\n * .plugins([myPlugin])\n * .myPlugin({ enableVoice: true }); // <-- Here\n * ```\n * \n *\n * ---\n *\n * The provided config can then be accessed from `plugin.config` inside handlers.\n *\n * @example\n * ```ts\n * const plugin = definePlugin(\"my-plugin\")\n * .config({ schema: z.object({ enableVoice: z.boolean() }) });\n * .addHandler({\n * name: \"handler-1\",\n * mode: \"block\",\n * onEvent: ({ plugin }) => {\n * if (plugin.config.enableVoice) { // <-- Here\n * // ...\n * }\n * },\n * });\n *\n * ```\n * \n *\n * ---\n * @param config - The config definition.\n * @returns TypedPluginBuilder\n */\n config<Schema extends z.ZodObject>(config: ZodObjectWithTelemetry<Schema, \"input\">) {\n return this.$config(zodObjectWithTelemetry(config));\n }\n\n /**\n * ### `.$config()`\n *\n * Define plugin config from the output of `definePluginConfig()`.\n *\n * @see TODO: Add docs link for `definePluginConfig()`\n *\n * ---\n * @param config - The config definition.\n * @returns TypedPluginBuilder\n */\n $config<ConfigDef extends PluginConfigDefinition>(config: ConfigDef) {\n const builder = new PluginBuilder({ ...this.def, config });\n return builder as TypedPluginBuilder<\n Override<PluginDef, \"config\", ConfigDef>,\n Excluded | \"config\" | \"$config\"\n >;\n }\n\n /**\n * ### `.context()`\n *\n * Add an in-memory object that handlers can read/write to share data without race conditions.\n *\n * Context is also exposed to dependent plugins and synced to the frontend via RPC.\n *\n * The context must be serializable, and thus cannot contain functions, instances, etc.\n * Use `state` property in handlers for non-serializable data instead.\n *\n * @see TODO: Add docs link\n *\n * @example\n * ```ts\n * const plugin = definePlugin(\"my-plugin\")\n * .context(z.object({ name: z.string() }));\n * ```\n * \n *\n * ---\n * @param context - The context definition.\n */\n context<Schema extends z.ZodObject>(context: ZodObjectWithTelemetry<Schema, \"input\">) {\n return this.$context(zodObjectWithTelemetry(context));\n }\n\n /**\n * ### `.$context()`\n *\n * Define plugin context from the output of `definePluginContext()`.\n *\n * @see TODO: Add docs link for `definePluginContext()`\n *\n * ---\n * @param context - The context definition.\n * @returns TypedPluginBuilder\n */\n $context<ContextDef extends PluginContextDefinition>(context: ContextDef) {\n const builder = new PluginBuilder({ ...this.def, context });\n return builder as TypedPluginBuilder<\n Override<PluginDef, \"context\", ContextDef>,\n Excluded | \"context\" | \"$context\"\n >;\n }\n\n /**\n * ### `.events()`\n *\n * Add events that handlers can emit and observe to communicate with each other.\n *\n * Each plugin has a single event queue enforcing the order of execution, making\n * plugins more predictable and easier to write and maintain.\n *\n * @see TODO: Add docs link\n *\n * @example\n * ```ts\n * const plugin = definePlugin(\"my-plugin\")\n * .events([{ name: \"my-event\" }])\n * .addHandler({\n * name: \"handler-1\",\n * mode: \"block\",\n * onEvent: ({ event, plugin }) => {\n * // Emit 'my-event' when the plugin starts\n * if (event.name === \"plugin.start\") {\n * plugin.events.emit({ name: \"my-event\" });\n * }\n * },\n * })\n * .addHandler({\n * name: \"handler-2\",\n * mode: \"block\",\n * onEvent: ({ event, plugin }) => {\n * // Listen to 'my-event' from handler-1\n * if (event.name === \"my-event\") {\n * // Do something...\n * }\n * },\n * });\n * ```\n * \n *\n * ---\n * @param events - The events definition.\n * @returns TypedPluginBuilder\n */\n events<const Names extends string[], EventsDef extends PluginEventsDefinition<Names>>(\n ...events: EventsDef\n ) {\n return this.$events([...internalEventsDef, ...events] as unknown as\n | typeof internalEventsDef\n | EventsDef);\n }\n\n /**\n * ### `.$events()`\n *\n * Define plugin events from the output of `definePluginEvents()`.\n *\n * @see TODO: Add docs link for `definePluginEvents()`\n *\n * ---\n * @param events - The events definition.\n * @returns TypedPluginBuilder\n */\n $events<EventsDef extends PluginEventsDefinition>(events: EventsDef) {\n const builder = new PluginBuilder({ ...this.def, events });\n return builder as TypedPluginBuilder<\n Override<PluginDef, \"events\", EventsDef>,\n Excluded | \"events\" | \"$events\"\n >;\n }\n\n /**\n * ### `.addHandler()`\n *\n * Add a handler function to react to events from this plugin or dependent plugins.\n *\n * @see TODO: Add docs link\n *\n * @example\n * ```ts\n * const plugin = definePlugin(\"my-plugin\")\n * .addHandler({\n * name: \"handler-1\",\n * mode: \"block\",\n * onEvent: ({ event, plugin }) => {\n * // Do something...\n * },\n * });\n * ```\n * \n *\n * ---\n *\n * Handlers can return a value, which can be retrieved using the `events.waitForResult(eventId, handlerName)` method.\n *\n * @example\n * ```ts\n * const plugin = definePlugin(\"my-plugin\")\n * .events([{ name: \"my-event\" }])\n * .addHandler({\n * name: \"handler-1\",\n * mode: \"block\",\n * onEvent: () => 123;\n * })\n * .addHandler({\n * name: \"handler-2\",\n * mode: \"block\",\n * onEvent: ({ event, plugin }) => {\n * if (event.name === \"my-event\") {\n * const eventId = plugin.events.emit({ name: \"my-event\" });\n * const result = await plugin.events.waitForResult(eventId, \"handler-1\");\n * // result: string (type-safe!)\n * }\n * },\n * })\n * ```\n * \n *\n * ---\n * @param handler - The handler definition.\n * @returns TypedPluginBuilder\n */\n addHandler<\n Name extends string,\n Handler extends PluginHandlerDefinition<PluginDef, Name, StateDef>,\n StateDef extends PluginHandlerStateDefinition<PluginDef>,\n >(handler: Handler & { state?: StateDef }) {\n const handlers = [...(this.def.handlers ?? []), handler];\n const builder = new PluginBuilder({ ...this.def, handlers });\n return builder as TypedPluginBuilder<\n // @ts-expect-error - type narrowing complaint, but works fine as is\n Override<\n PluginDef,\n \"handlers\",\n [\n ...PluginDef[\"handlers\"],\n {\n name: Handler[\"name\"];\n mode: Handler[\"mode\"];\n state: never; // State is internal, no need to store its type\n onEvent: (params: unknown) => ReturnType<Handler[\"onEvent\"]>; // Omit params to reduce type explosion\n },\n ]\n >,\n Excluded\n >;\n }\n\n /**\n * ### `.removeHandler()`\n *\n * Remove a previously added handler from the plugin.\n *\n * @see TODO: Add docs link\n *\n * @example\n * ```ts\n * const plugin = definePlugin(\"my-plugin\")\n * .addHandler({\n * name: \"handler-1\",\n * mode: \"block\",\n * onEvent: ({ event, plugin }) => void 0,\n * });\n *\n * const pluginWithoutHandler1 = plugin.removeHandler(\"handler-1\");\n * ```\n * \n *\n * ---\n * @param name - The name of the handler to remove.\n * @returns TypedPluginBuilder\n */\n removeHandler<const Name extends PluginDef[\"handlers\"][number][\"name\"]>(name: Name) {\n const handlers = this.def.handlers.filter((h) => h.name !== name);\n const builder = new PluginBuilder({ ...this.def, handlers });\n return builder as TypedPluginBuilder<\n Override<PluginDef, \"handlers\", FilterHandlersByName<PluginDef[\"handlers\"], Name>>,\n Excluded\n >;\n }\n}\n\n/**\n * ### `definePlugin()`\n *\n * Define a new Life.js plugin.\n *\n * @see TODO: Add docs link\n */\nexport function definePlugin<const Name extends string>(name: Name) {\n return new PluginBuilder({\n name,\n dependencies: [],\n config: zodObjectWithTelemetry({ schema: z.object() }),\n context: zodObjectWithTelemetry({ schema: z.object() }),\n events: internalEventsDef,\n handlers: [],\n });\n}\n","import { z } from \"zod\";\nimport { deepClone } from \"@/shared/deep-clone\";\nimport * as op from \"@/shared/operation\";\nimport { newId } from \"@/shared/prefixed-id\";\n\n// Base message\nexport const baseMessageSchema = {\n id: z.string(),\n createdAt: z.number(),\n lastUpdated: z.number(),\n};\n\n// User message\nexport const userMessageSchema = z.object({\n ...baseMessageSchema,\n role: z.literal(\"user\"),\n content: z.string().prefault(\"\"),\n});\nexport type UserMessage = z.output<typeof userMessageSchema>;\n\n// System message\nexport const systemMessageSchema = z.object({\n ...baseMessageSchema,\n role: z.literal(\"system\"),\n content: z.string().prefault(\"\"),\n});\nexport type SystemMessage = z.output<typeof systemMessageSchema>;\n\n// Tool request\nexport const agentToolRequestSchema = z.object({\n toolRequestId: z.string(),\n toolName: z.string(),\n toolInput: z.record(z.string(), z.any()),\n});\nexport type AgentToolRequest = z.output<typeof agentToolRequestSchema>;\n\n// Agent message\nexport const agentMessageSchema = z.object({\n ...baseMessageSchema,\n role: z.literal(\"agent\"),\n content: z.string().prefault(\"\"),\n toolsRequests: z.array(agentToolRequestSchema).prefault([]),\n});\nexport type AgentMessage = z.output<typeof agentMessageSchema>;\n\n// Tool response message\nexport const toolResponseMessageSchema = z.object({\n ...baseMessageSchema,\n role: z.literal(\"tool\"),\n toolRequestId: z.string(),\n toolName: z.string(),\n toolSuccess: z.boolean(),\n toolOutput: z.record(z.string(), z.any()).optional(),\n toolError: z.string().optional(),\n});\n\nexport type ToolResponseMessage = z.output<typeof toolResponseMessageSchema>;\n\n// Message\nexport const messageSchema = z.discriminatedUnion(\"role\", [\n userMessageSchema,\n systemMessageSchema,\n agentMessageSchema,\n toolResponseMessageSchema,\n]);\n\nexport type Message = z.output<typeof messageSchema>;\n\n// Create message input\nconst createOmitFields = { createdAt: true, lastUpdated: true, id: true } as const;\nexport const createMessageInputSchema = z.discriminatedUnion(\"role\", [\n userMessageSchema.omit(createOmitFields),\n systemMessageSchema.omit(createOmitFields),\n agentMessageSchema.omit(createOmitFields),\n toolResponseMessageSchema.omit(createOmitFields),\n]);\nexport type CreateMessageInput = z.input<typeof createMessageInputSchema>;\n\n// Update message input\nconst updateOmitFields = { createdAt: true, lastUpdated: true, id: true } as const;\nexport const updateMessageInputSchema = z.discriminatedUnion(\"role\", [\n userMessageSchema\n .omit(updateOmitFields)\n .partial()\n .extend({ role: z.literal(\"user\") }),\n systemMessageSchema\n .omit(updateOmitFields)\n .partial()\n .extend({ role: z.literal(\"system\") }),\n agentMessageSchema\n .omit(updateOmitFields)\n .partial()\n .extend({ role: z.literal(\"agent\") }),\n toolResponseMessageSchema\n .omit(updateOmitFields)\n .partial()\n .extend({ role: z.literal(\"tool\") }),\n]);\nexport type UpdateMessageInput<T extends Message[\"role\"]> = Extract<\n z.input<typeof updateMessageInputSchema>,\n { role: T }\n>;\n\n// export type AgentMessageUpdateInput = Extract<UpdateMessageInput<\"agent\">, { role: \"agent\" }>;\n\n/**\n * A helper class aimed at facilitating safe and efficient\n * manipulation of an array of messages.\n * @param messages - Optionally, the messages to initialize the list with.\n */\nexport class MessageList {\n #messages: Message[] = [];\n\n constructor(messages?: Message[]) {\n this.#messages = deepClone(messages ?? []);\n }\n\n getAll() {\n return op.attempt(() => deepClone(this.#messages));\n }\n\n get(id: string) {\n const [err, messages] = this.getAll();\n if (err) return op.failure(err);\n return op.success(messages.find((message) => message.id === id));\n }\n\n findLastFromRoles<R extends Message[\"role\"]>(roles: readonly R[]) {\n const [err, messages] = this.getAll();\n if (err) return op.failure(err);\n const lastMessage = messages.reverse().find((message) => roles.includes(message.role as R)) as\n | Extract<Message, { role: R }>\n | undefined;\n return op.success(lastMessage);\n }\n\n create(message: CreateMessageInput) {\n // Validate the message input\n const { data: validatedMessage, error: validatedMessageError } =\n createMessageInputSchema.safeParse(message);\n if (validatedMessageError)\n return op.failure({\n code: \"Validation\",\n message: \"Invalid message shape.\",\n cause: validatedMessageError,\n });\n\n // Else, create and insert the message\n const newMessage: Message = {\n ...validatedMessage,\n id: newId(\"message\"),\n createdAt: Date.now(),\n lastUpdated: Date.now(),\n };\n this.#messages.push(newMessage);\n\n // Return the message id\n return op.success(newMessage.id);\n }\n\n update<Role extends Message[\"role\"]>(\n id: string,\n role: Role,\n message: Omit<UpdateMessageInput<Role>, \"role\">,\n ) {\n // Validate the message input (include role for discriminated union)\n const { data: validatedMessage, error: validationError } = updateMessageInputSchema.safeParse({\n ...message,\n role,\n });\n if (validationError)\n return op.failure({\n code: \"Validation\",\n message: \"Invalid message shape.\",\n cause: validationError,\n });\n\n // If the message does not exist, return a failure\n const [err, existingMessage] = this.get(id);\n if (err) return op.failure(err);\n if (!existingMessage)\n return op.failure({\n code: \"NotFound\",\n message: `Message with id '${id}' does not exist.`,\n });\n\n // Ensure the role is matching the message role\n if (existingMessage.role !== role)\n return op.failure({\n code: \"Validation\",\n message: \"Invalid message role provided.\",\n cause: new Error(`Message with id '${id}' is not a ${role} message.`),\n });\n\n // Build the new message object\n const newMessage = {\n ...existingMessage,\n ...validatedMessage,\n lastUpdated: Date.now(),\n } as Message;\n\n // Update the message in the list\n this.#messages = this.#messages.map((m) => (m.id === id ? newMessage : m));\n\n // Return the message id\n return op.success(id);\n }\n}\n","import z from \"zod\";\nimport { definePluginConfig } from \"@/plugins/server/builder\";\nimport type { PluginConfig } from \"@/plugins/server/types\";\n\nexport const generationPluginConfigDef = definePluginConfig({\n schema: z.object({\n voiceDetection: z\n .object({\n /**\n * VAD score threshold for detecting voice activity during speech (0.0-1.0).\n * Uses hysteresis: once voice is detected, this higher threshold must be exceeded\n * to continue considering audio as speech. This prevents flickering between\n * speech/silence states during continuous speaking.\n */\n scoreInThreshold: z.number().prefault(0.5),\n /**\n * VAD score threshold for detecting end of voice activity (0.0-1.0).\n * When voice is active, audio must fall below this lower threshold to be\n * considered silence. Lower than `scoreInThreshold` to provide hysteresis.\n */\n scoreOutThreshold: z.number().prefault(0.25),\n /**\n * Number of silent audio chunks to buffer before voice starts (default: 100 ≈ 1s).\n * These chunks are emitted when voice is detected, ensuring the first syllables\n * and word onsets are captured for better STT accuracy.\n *\n * Can slightly impact STT latency, as increases the amount of audio to be processed.\n */\n prePaddingChunks: z.number().prefault(100),\n /**\n * Number of silent chunks to append after voice ends before finalizing (default: 200 ≈ 2s).\n *\n * Most STT providers require silence padding to finalize transcription. For example,\n * Deepgram with `endpointing: 0` and `no_delay: true` still needs substantial\n * silence to return results. Too few chunks may cause the STT to hang indefinitely.\n *\n * This default (200) balances latency and stability across providers. If your STT\n * finalizes transcripts quickly, consider lowering this value. Benchmark carefully.\n */\n postPaddingChunks: z.number().prefault(200),\n /**\n * Minimum duration (in ms) of continuous voice to trigger agent interruption.\n * Uses a sliding window to accumulate voice chunks and filter out VAD false positives.\n * Only when accumulated voice duration exceeds this threshold, the agent will be interrupted.\n */\n minVoiceInterruptionMs: z.number().prefault(50),\n })\n .prefault({}),\n endOfTurnDetection: z\n .object({\n /**\n * Probability threshold (0.0-1.0) that determines when the user has finished speaking.\n * If EOU model confidence >= threshold, agent responds immediately. Otherwise, waits\n * with an adaptive timeout (see min/maxTimeoutMs below).\n *\n * Tuning considerations:\n * - Too low: Agent may interrupt users mid-sentence, creating awkward overlaps\n * - Too high: Agent waits longer before responding, increasing perceived latency\n * - Default (0.6): Balanced trade-off between responsiveness and avoiding interruptions\n */\n threshold: z.number().prefault(0.6),\n /**\n * Fallback timeout (in ms) ensuring the agent eventually responds even when EOU\n * confidence stays low. Prevents the agent from waiting indefinitely if the model\n * never reaches the threshold (e.g., incomplete sentences, uncertain patterns).\n */\n minTimeoutMs: z.number().prefault(300),\n /**\n * Maximum wait time (in ms) when EOU confidence is at or near zero. As confidence\n * increases, the timeout shrinks adaptively toward minTimeoutMs using:\n * `timeout = max(minTimeoutMs, maxTimeoutMs * (1 - probability / threshold))`\n *\n * This creates natural turn-taking: high confidence = quick response, low confidence = patient waiting.\n */\n maxTimeoutMs: z.number().prefault(5000),\n })\n .prefault({}),\n }),\n});\n\nexport type GenerationPluginConfig<T extends \"input\" | \"output\"> = PluginConfig<\n typeof generationPluginConfigDef,\n T\n>;\n","import z from \"zod\";\nimport { definePluginContext } from \"@/plugins/server/builder\";\nimport type { PluginContext } from \"@/plugins/server/types\";\nimport { messageSchema } from \"@/shared/messages\";\nimport { generationStatusSchema } from \"./status\";\n\nexport const generationPluginContextDef = definePluginContext({\n schema: z.object({\n /**\n * The entire history of messages handled by the generation plugin.\n */\n messages: z.array(messageSchema).prefault([\n {\n id: \"system-1\",\n createdAt: Date.now(),\n lastUpdated: Date.now(),\n role: \"system\",\n content: ` \n# General Instructions\nThe below instructions define your general behavior and attitude as an agent.\nFurther down, you'll find more specific instructions about your role, your context, and your capabilities.\n\n## Attitude\nThe user is speaking to you using their voice and/or by typing on a chat interface (if available).\nYour answers are streamed back to the user as text and/or voice as well. \n\nAs a multimodal agent, your answers must sound natural when converted to voice, but also when read as text.\nHere's how to produce **spoken language**:\n\n- Use a natural, informal, conversational vocabulary and tone. You're original, have your own personality.\n Example: \"Yeah I think I got your point, but are you sure this would work for all configurations?\"\n\n- Include filler words and hesitations (e.g., \"um\", \"uh\", \"like\", \"oh\", \"ah\") where appropriate.\n Example: \"Um, let me think... maybe we could try that approach?\"\n \n- Embrace incomplete sentences or broken thoughts to simulate true speech patterns.\n Example: \"Maybe we should... oh wait, here's another idea...\"\n\n- If a user asks something like \"can you hear me?\", say yes, you can hear them.\n\n- Remember that the user can interrupt you at any time, handle that gracefully.\n\n- Show traits of emotional intelligence like self-awareness, empathy, humor, excitment.\n\n- Employ small-talks, both at the beginning and during the conversation, as long as it doesn't disrupt the conversation flow.\n\n- Relate to the user's answers, so they feel heard. Don't repeat their entire answers, but just small notes and summaries showing that you're listening and keeping track of the conversation.\n\n- Tend to mirror the user's expression, words, etc. It is proven that it increases the rapport.\n\n- Overall, be positive, open and warm to the users ideas, encourage them to talk more about themselves.\n\n## Markdown\nYour answers can include Markdown following the CommonMark, GFM, LaTex, and Mermaid specifications.\n\nBefore being converted to voice with a TTS model, your answers will be pre-processed as follows:\n\n- Markdown symbols will be stripped out, e.g., \"**bold**\" becomes \"bold\"\n\n- The following blocks will be entirely ignored from the TTS speech: table, code blocks, LaTeX blocks and Mermaid blocks.\n So if you need to include comment about such a block, do it right before or after the block.\n Example: \n \\`\\`\\`markdown\n You were right, there is a **huge** spike in February!\n | Month | Value |\n |-------|-------|\n | January | 100 |\n | February | 1380 (huge spike) |\n | March | 300 |\n | April | 350 |\n | May | 200 |\n \n Do you want me to investigate further?\n \\`\\`\\`\n will be converted as voice as \"You were right, there is a huge spike in February! Do you want me to investigate further?\"\n with a short pause in speech when the table is being rendered.\n\n- While LaTeX blocks are excluded from speech, inline LaTeX is converted into spoken form.\n Example: \"$x^2$\" becomes \"x squared\", \"$y = x^2 + 1$\" becomes \"y equals x squared plus one\".\n Avoid using long inline LaTeX, only simple and short formulas or variable names. Use LaTeX blocks for the rest.\n\n- While code blocks are excluded from speech, inline code is converted into plain text with symbol stripped out.\n Example: \"Call the \\`generate()\\` function\" becomes \"Call the generate function\".\n Avoid using long inline code, only simple and short code snippets, like variables or functions names. Use code blocks for the rest.\n\n- Links, images, and their references are converted into just the alt or title text when provided.\n Example: \"[link](https://example.com)\" becomes \"link\", \"\" becomes \"image\".\n If an alt of title is not provided, the image or link won't appear in the speech.\n Example: \"Can you check https://example.com ?\" becomes \"Can you check ?\" which might be confusing.\n\n- List are converted into plain text, without delimiters symbols, even for ordered lists. So make your list items content sounding natural when read.\n Example:\n \"Here are the steps to follow:\n 1. First, open the app.\n 2. Then, click on the \"+\" button.\n 3. Finally, reload the page.\n \"\n will be converted as voice as \"Here are the steps to follow: First, open the app. Then, click on the plus button. Finally, reload the page.\"\n\nSo for ordered lists for example, when numbering matters, \n\n## Bug / Error handling\nIf something doesn't work as expected, retry with the user 2 times, then if it still doesn't work:\n- Notify the developers by sending them a message using the \"notify_developers\" tool.\n- Then only, explain to the user that there might be some temporary issue right now, that you've notified the developers, and ask the user to try again later.\n- Don't bother the user with too many retry back and forth, if you see that it's not working after two retries, just notify the devs and move on.\n `,\n },\n ]),\n /**\n * The current generation status.\n * Contains { listening, thinking, speaking } flags.\n */\n status: generationStatusSchema,\n /**\n * Whether the generation plugin should generate and stream voice back\n * to the user. If true, solely text chunks will be emitted.\n */\n voiceEnabled: z.boolean().prefault(true),\n }),\n});\n\nexport type GenerationPluginContext<T extends \"input\" | \"output\"> = PluginContext<\n typeof generationPluginContextDef,\n T\n>;\n","import z from \"zod\";\nimport * as op from \"@/shared/operation\";\n\nexport const generationStatusSchema = z\n .object({\n listening: z.boolean().prefault(true),\n thinking: z.boolean().prefault(false),\n speaking: z.boolean().prefault(false),\n })\n .prefault({});\n\nexport type GenerationStatus = z.infer<typeof generationStatusSchema>;\n\nexport const computeStatus = (oldStatus: GenerationStatus, eventType: string) => {\n try {\n if (eventType === \"agent.thinking-start\") {\n return op.success({ ...oldStatus, listening: false, thinking: true });\n } else if (eventType === \"agent.thinking-end\") {\n return op.success({ ...oldStatus, thinking: false });\n } else if (eventType === \"agent.speaking-end\") {\n return op.success({ ...oldStatus, listening: true, thinking: false, speaking: false });\n } else if (eventType === \"agent.speaking-start\") {\n return op.success({ ...oldStatus, listening: false, speaking: true });\n }\n return op.success(oldStatus);\n } catch (error) {\n return op.failure({ code: \"Unknown\", cause: error });\n }\n};\n","import z from \"zod\";\nimport { llmResourcesSchema } from \"@/models/llm/resources\";\nimport { definePluginEvents } from \"@/plugins/server/builder\";\nimport type { PluginEvent } from \"@/plugins/server/types\";\nimport { agentToolRequestSchema, createMessageInputSchema, messageSchema } from \"@/shared/messages\";\n\nconst insertEventBaseSchema = z.object({\n interrupt: z.enum([\"abrupt\", \"smooth\"]).or(z.literal(false)).prefault(\"abrupt\"),\n preventInterruption: z.boolean().prefault(false),\n});\n\nexport const generationPluginEventsDef = definePluginEvents(\n { name: \"messages.create\", dataSchema: z.object({ message: createMessageInputSchema }) },\n {\n name: \"messages.update\",\n dataSchema: z.object({\n id: z.string(),\n role: z.enum([\"user\", \"system\", \"agent\", \"tool\"]),\n message: createMessageInputSchema,\n }),\n },\n\n { name: \"user.audio-chunk\", dataSchema: z.object({ audioChunk: z.custom<Int16Array>() }) },\n { name: \"user.voice-start\" },\n {\n name: \"user.voice-chunk\",\n dataSchema: z.discriminatedUnion(\"type\", [\n z.object({ type: z.literal(\"voice\"), voiceChunk: z.custom<Int16Array>() }),\n z.object({\n type: z.literal(\"padding\"),\n voiceChunk: z.custom<Int16Array>(),\n paddingSide: z.enum([\"pre\", \"post\"]),\n paddingIndex: z.number(),\n }),\n ]),\n },\n { name: \"user.voice-end\" },\n { name: \"user.text-chunk\", dataSchema: z.object({ textChunk: z.string() }) },\n { name: \"user.interrupted\" },\n\n { name: \"agent.thinking-start\" },\n { name: \"agent.thinking-end\" },\n { name: \"agent.speaking-start\" },\n { name: \"agent.speaking-end\" },\n { name: \"agent.continue\", dataSchema: insertEventBaseSchema },\n {\n name: \"agent.decide\",\n dataSchema: insertEventBaseSchema.extend({ messages: z.array(messageSchema) }),\n },\n {\n name: \"agent.say\",\n dataSchema: insertEventBaseSchema.extend({ text: z.string() }),\n },\n {\n name: \"agent.interrupt\",\n dataSchema: z.object({\n reason: z.string(),\n author: z.enum([\"user\", \"application\"]),\n force: z.boolean().prefault(false),\n }),\n },\n { name: \"agent.resources-request\" },\n {\n name: \"agent.resources-response\",\n dataSchema: z.object({ requestId: z.string(), resources: llmResourcesSchema }),\n },\n {\n name: \"agent.tool-requests\",\n dataSchema: z.object({ requests: z.array(agentToolRequestSchema) }),\n },\n {\n name: \"agent.interrupted\",\n dataSchema: z.object({\n reason: z.string(),\n forced: z.boolean(),\n author: z.enum([\"user\", \"application\"]),\n }),\n },\n { name: \"agent.text-chunk\", dataSchema: z.object({ textChunk: z.string() }) },\n { name: \"agent.voice-chunk\", dataSchema: z.object({ voiceChunk: z.custom<Int16Array>() }) },\n);\n\nexport type GenerationPluginEvent<T extends \"input\" | \"output\"> = PluginEvent<\n typeof generationPluginEventsDef,\n T\n>;\n","import z from \"zod\";\nimport { messageSchema } from \"@/shared/messages\";\n\n// Tools\nexport const llmToolSchema = z.object({\n name: z.string(),\n description: z.string(),\n schema: z.object({\n input: z.instanceof(z.ZodObject),\n output: z.instanceof(z.ZodObject),\n }),\n run: z.function({\n input: [z.record(z.string(), z.any())],\n output: z.union([\n z.object({\n success: z.boolean(),\n output: z.record(z.string(), z.any()).optional(),\n error: z.string().optional(),\n }),\n z.promise(\n z.object({\n success: z.boolean(),\n output: z.record(z.string(), z.any()).optional(),\n error: z.string().optional(),\n }),\n ),\n ]),\n }),\n});\n\n/**\n * Represents a tool definition for an LLM.\n */\nexport type LLMToolDefinition = z.infer<typeof llmToolSchema>;\n\n// Resources\nexport const llmResourcesSchema = z.object({\n messages: z.array(messageSchema),\n tools: z.array(llmToolSchema),\n});\n\n/**\n * Represents the common set of resources passed to an LLM for a generation job.\n */\nexport type LLMResources = z.infer<typeof llmResourcesSchema>;\n","import { z } from \"zod\";\nimport type { AgentServer } from \"@/agent/server/class\";\nimport type { LLMResources } from \"@/models/llm/resources\";\nimport type { PluginAccessor } from \"@/plugins/server/types\";\nimport { AsyncQueue } from \"@/shared/async-queue\";\nimport type * as op from \"@/shared/operation\";\nimport { newId } from \"@/shared/prefixed-id\";\nimport type { generationPlugin } from \".\";\nimport type { GenerationPluginEvent } from \"./events\";\nimport { Generation, type GenerationChunk } from \"./generation\";\n\n// Orchestrator\nexport class GenerationOrchestrator {\n readonly #agent: op.ToPublic<AgentServer>;\n readonly #plugin: op.ToPublic<\n PluginAccessor<(typeof generationPlugin)[\"def\"], { type: \"server\"; name: string }>\n >;\n readonly #eventsQueue = new AsyncQueue<GenerationPluginEvent<\"output\">>();\n readonly #generationsQueue: AsyncQueue<Generation> = new AsyncQueue();\n #generations: Generation[] = [];\n #decidePromises: {\n id: string;\n event: Extract<GenerationPluginEvent<\"output\">, { name: \"agent.decide\" }>;\n promise: Promise<void>;\n cancel: () => Extract<GenerationPluginEvent<\"output\">, { name: \"agent.decide\" }>;\n }[] = [];\n readonly #generationsResourcesRequestsIds: Record<string, string> = {};\n readonly #resourcesResponses: Record<string, LLMResources> = {};\n\n constructor(params: {\n plugin: op.ToPublic<\n PluginAccessor<(typeof generationPlugin)[\"def\"], { type: \"server\"; name: string }>\n >;\n agent: op.ToPublic<AgentServer>;\n }) {\n this.#agent = params.agent;\n this.#plugin = params.plugin;\n }\n\n pushEvent(event: GenerationPluginEvent<\"output\">) {\n this.#eventsQueue.push(event);\n }\n\n async start() {\n // Start consuming generations\n this.#consumeGenerations();\n\n // Start processing generation even, process it\n for await (const event of this.#eventsQueue) {\n if (this.#isGenerationEvent(event)) await this.#processGenerationEvent(event);\n }\n }\n\n #createGeneration() {\n // Create the generation\n const generation = new Generation({\n agent: this.#agent,\n voiceEnabled: this.#plugin.context.get().voiceEnabled,\n });\n this.#generations.push(generation);\n\n // On generation status change, try to update the thinking status\n generation.onStatusChange(() => {\n const runningCount = this.#generations.filter((g) => g.progress === \"started\").length;\n // - If the agent is thinking, but no generation is running, emit thinking end\n if (this.#plugin.context.get().status.thinking) {\n if (runningCount === 0)\n this.#plugin.events.emit({ name: \"agent.thinking-end\", urgent: true });\n }\n // - Or if the agent is not thinking, but a generation is running, emit thinking start\n else if (runningCount > 0)\n this.#plugin.events.emit({ name: \"agent.thinking-start\", urgent: true });\n });\n\n return generation;\n }\n\n async #processGenerationEvent(event: GenerationPluginEvent<\"output\">) {\n // Retrieve or create the first idle generation\n let generation = this.#generations.find((g) => g.progress === \"idle\");\n if (!generation) generation = await this.#createGeneration();\n\n // Process the event\n if (event.name === \"agent.continue\") this.#processInsertEvent(generation, event);\n else if (event.name === \"agent.say\") this.#processInsertEvent(generation, event);\n else if (event.name === \"agent.decide\") this.#processDecideEvent(generation, event);\n else if (event.name === \"agent.i