Skip to content

Agent SDK Upgrade Guide

Start with Migrate to a self-hosted backend for the backend, database, history, and push steps. This guide lists changes from main at cc878025da to self-hosted at 610175f3b3. A call can keep its name and still have different parameters, results, or behavior. The Before examples use the old SDK. The After examples use the new SDK.

The package stays @xmtp/agent-sdk. Its source moved from sdks/js/agent-sdk to sdks/agent. The minimum Node.js version changed from 22 to 22.12.0.

The Agent SDK re-exports the Node SDK. Apply the Node SDK Upgrade Guide to agent.client, ctx.client, and ctx.conversation. The Agent changes below cover the additional Agent API.

Use SDK 8.0.0-rc1 with backend 8.0.0-rc1. Run this command from your app directory to save the exact SDK version:

Terminal window
npm install --save-exact @xmtp/agent-sdk@8.0.0-rc1

See Install the XMTP SDK for platform requirements.

Agent.create(signer, options) now requires options with an explicit backend and storage. An environment name no longer selects a public network. Replace apiUrl or gatewayHost with backend.url, including its scheme. Put appVersion under backend. Supply backend authentication through backend.credentials.

Old optionNew option or action
envstorage.label; this is a directory label only
dbPath: stringstorage.location: { dbPath, attachmentsDir }
dbPath: (inboxId) => pathCompute an explicit location, or use storage.location: { directory }
dbPath: nullstorage.location: "inMemory"
dbEncryptionKeystorage.encryptionKey, as 32 bytes
disableDeviceSync: truedeviceSync: false
disableAutoRegister: trueregistration: { auto: false }
nonceregistration.nonce; set 1n explicitly when preserving the old default inbox derivation
minDbPoolSize, maxDbPoolSizestorage.pool.min, storage.pool.max
useSingleConnectionstorage.singleConnection
workerConfigworkers, with a default interval and per-worker interval records
historySyncUrlRemoved; the backend supplies device-sync configuration
waitForRegistrationVisibleRemoved; no registration quorum option
unstableChangeCallbacks.appDataRemoved from creation options; use the client event API
stdoutLoggingLevelRemoved; no separate stdout level
otelEndpoint, resourceAttributesinitLogging({ otel: { endpoint, serviceName }, resourceAttributes }); attributes use a Map
loggingLevel, structuredLoggingCall initLogging({ level, structured })
codecsStill supported; use the Node SDK codec interface with .type

Agent creation still sets device sync off by default. It sets backend.appVersion to agent-sdk/<version> when the caller supplies URL options without an app version. Other Node options are now grouped under forkRecovery, workers, handlers, and attachments. allowOffline permits startup from an existing store when the backend is unavailable.

Before:

Before (main cc878025da)
import { Agent, createSigner, createUser } from "@xmtp/agent-sdk";
const agent = await Agent.create(createSigner(createUser()), {
env: "production",
dbPath: (inboxId) => `./agent-data/xmtp-${inboxId}.db3`,
disableDeviceSync: true,
});

After:

const
const user: User
user
=
function createUser(key?: Hex, chain?: Chain): User

Create a viem wallet, generating a private key when one is not supplied.

createUser
();
const
const signer: Signer
signer
=
function createSigner(user: User): Signer

Adapt a viem wallet to the byte-returning XMTP signer interface.

createSigner
(
const user: User
user
);
const
const agent: Agent<unknown>
agent
= await
class Agent<ContentTypes = unknown>

Event-driven XMTP agent that routes conversations and messages to middleware.

Agent
.
Agent<ContentTypes = unknown>.create<[]>(signer: Parameters<(signer: Signer, options: ClientOptions) => Promise<Client>>[0], options: AgentCreateOptions<[]>): Promise<Agent<unknown>>

Create an agent and client. backend.credentials supplies backend credentials. Device sync defaults to disabled.

create
(
const signer: Signer
signer
, {
backend?: BackendSource | undefined
backend
: {
url: string
url
: "https://your-backend.example.com" },
storage: StorageOptions
storage
: {
location: StorageLocation
location
: {
directory: string
directory
: "./agent-data" } },
deviceSync?: boolean | undefined
deviceSync
: false,
});

createUser(key?, chain?) keeps its arguments. Its key type is now Hex from viem. createIdentifier(user) returns { identifier, kind: "ethereum" } instead of { identifier, identifierKind: IdentifierKind.Ethereum }. createSigner(user) creates the new signer interface: async identity(), async kind(), and sign({ text }), which returns { kind: "ecdsa", value }. Replace custom signers that use type, getIdentifier(), or signMessage().

Environment variables and stored databases

Section titled “Environment variables and stored databases”

Before you call createFromEnv(), set XMTP_WALLET_KEY to a hex private key with a 0x prefix. This variable is required. For an encrypted database, set XMTP_DB_ENCRYPTION_KEY to the stored 32-byte key as 64 hex characters, with or without 0x. This variable is optional. Keep the same key across restarts. These setup requirements apply before and after the upgrade.

VariableChange
XMTP_BACKEND_URLRequired unless options supply backend; overrides a URL in options
XMTP_GATEWAY_HOSTNo longer read; use XMTP_BACKEND_URL
XMTP_ENVAny safe directory label; no network selection
XMTP_DB_DIRECTORYStorage root; creates a missing directory with mode 0700 where supported
XMTP_FORCE_DEBUG_LEVELStill read; sets structured logging through initLogging; invalid values use warn

Load .env into process.env before the call. createFromEnv() does not read .env itself. Its options are now Partial<AgentCreateOptions>. Caller-supplied storage overrides the directory variable and skips the legacy database search. storage.encryptionKey, when supplied, overrides XMTP_DB_ENCRYPTION_KEY.

const
const environmentAgent: Agent<unknown>
environmentAgent
= await
class Agent<ContentTypes = unknown>

Event-driven XMTP agent that routes conversations and messages to middleware.

Agent
.
Agent<ContentTypes = unknown>.createFromEnv<[]>(options?: Partial<AgentCreateOptions<[]>> | undefined): Promise<Agent<unknown>>

Create an agent from XMTP_* variables. XMTP_BACKEND_URL overrides options.backend; one must be supplied. Pass backend.credentials in options for backend authentication.

createFromEnv
({
storage?: StorageOptions | undefined
storage
: {
location: StorageLocation
location
: {
directory: string
directory
: "./agent-data" } },
});

A new store uses ./xmtp or XMTP_DB_DIRECTORY as its root. A non-empty XMTP_ENV adds a label directory. The store contains {root}/{label}/{deployment}/{inbox-id}/xmtp.db3 and a sibling attachments directory. The label component is omitted when empty. Preserve the whole storage root, its deployment record, and the same encryption key across restarts.

createFromEnv() can select one matching legacy database filename. This supports only a database already in the current format; it cannot open or convert a pre-transition database from main. Keep the old database as a backup and choose new storage for migration. With XMTP_DB_DIRECTORY, it checks xmtp-<inbox-id>.db3. Without that variable, it checks the working directory for xmtp-<env>-<inbox-id>.db3. XMTP_ENV limits that second search when set. It matches the signer, including the old default nonce 1n; files for other inboxes do not prevent startup. More than one matching legacy file, or both a legacy and current store for that inbox, causes an error. Pass storage.location: { dbPath, attachmentsDir } to select the intended database. This selection does not move the file.

The context classes and most method names remain. Their underlying client is now the non-generic Node Client. Their message is the new Message with tagged content.

Old code or typeNew code or type
Text from ctx.message.contentText from ctx.content, after ctx.isText() or in a text listener
Assign ctx.message.contentAssign ctx.content; the SDK message stays unchanged
DecodedMessage<T>Message; inspect message.content.kind
ctx.isAllowed, ctx.isDenied, ctx.isUnknownawait ctx.isAllowed(), await ctx.isDenied(), await ctx.isUnknown()
Synchronous conversation consent accessawait ctx.consentState() returns "allowed", "denied", or "unknown"
filter.isGroupAdmin(...)await filter.isGroupAdminAsync(...)
filter.isGroupSuperAdmin(...)await filter.isGroupSuperAdminAsync(...)
usesCodec matches codec.contentTypeIt matches codec.type: authority, type name, and major version
Read-receipt payloadctx.content is undefined
Reply / EnrichedReply payloadTagged reply record: kind, referenceId, body; enrichment stays on ctx.message
Reaction enumsString values, such as schema "unicode"

getClientAddress(), agent.address, and getSenderAddress() keep their names. The first two read client.identity.identifier. The sender helper reads the first identity from client.inboxStates([senderInboxId], false).

filter.hasContent now accepts built-in read receipts. It rejects unknown content and custom content without a decoded value. filter.usesCodec requires decoded content and matches the new codec record.

Before:

Before (main cc878025da)
agent.on("text", async (ctx) => {
console.log(ctx.message.content);
if (ctx.isAllowed) await ctx.sendTextReply("Received");
});
agent.use(async (ctx, next) => {
if (!filter.isGroupAdmin(ctx.conversation, ctx.message)) return;
await next();
});

After:

const agent: Agent<unknown>
agent
.
NodeJS.EventEmitter<EventHandlerMap<unknown>>.on<"text">(eventName: keyof EventEmitterEventMap | "actions" | "attachment" | "conversation" | "group-update" | "dm" | "group" | "inline-attachment" | "intent" | "leave-request" | "markdown" | "message" | "multi-attachment" | "reaction" | "read-receipt" | "reply" | "start" | "stop" | "text" | "transaction-reference" | "unhandledError" | "unknownMessage" | "wallet-send-calls", listener: (ctx: MessageContext<string, unknown>) => void): Agent<unknown>

Adds the listener function to the end of the listeners array for the event named eventName. No checks are made to see if the listener has already been added. Multiple calls passing the same combination of eventName and listener will result in the listener being added, and called, multiple times.

server.on('connection', (stream) => {
console.log('someone connected!');
});

Returns a reference to the EventEmitter, so that calls can be chained.

By default, event listeners are invoked in the order they are added. The emitter.prependListener() method can be used as an alternative to add the event listener to the beginning of the listeners array.

import { EventEmitter } from 'node:events';
const myEE = new EventEmitter();
myEE.on('foo', () => console.log('a'));
myEE.prependListener('foo', () => console.log('b'));
myEE.emit('foo');
// Prints:
// b
// a

@since ― v0.1.101

@param ― eventName The name of the event.

@param ― listener The callback function

on
("text", async (
ctx: MessageContext<string, unknown>
ctx
) => {
var console: Console
console
.
Console.log(...data: any[]): void

The console.log() static method outputs a message to the console.

MDN Reference

log
(
ctx: MessageContext<string, unknown>
ctx
.
MessageContext<string, unknown>.content: string

Replace the middleware value without changing the SDK message.

The decoded value selected by the content type.

content
);
var console: Console
console
.
Console.log(...data: any[]): void

The console.log() static method outputs a message to the console.

MDN Reference

log
(
ctx: MessageContext<string, unknown>
ctx
.
MessageContext<string, unknown>.message: Message

The SDK message, including its tagged content and retained bytes.

message
.
Message.content: MessageContent
content
.
kind: "actions" | "attachment" | "intent" | "markdown" | "reaction" | "reply" | "text" | "readReceipt" | "remoteAttachment" | "multiRemoteAttachment" | "transactionReference" | "walletSendCalls" | "groupUpdated" | "leaveRequest" | "deletedMessage" | "custom" | "unknown"
kind
);
if (await
ctx: MessageContext<string, unknown>
ctx
.
ConversationContext<unknown, Conversation>.isAllowed(): Promise<boolean>

Whether the conversation consent state is allowed.

isAllowed
()) {
await
ctx: MessageContext<string, unknown>
ctx
.
MessageContext<string, unknown>.sendTextReply(text: string): Promise<void>

Send a text reply to this message with push disabled.

sendTextReply
("Received");
await
ctx: MessageContext<string, unknown>
ctx
.
MessageContext<string, unknown>.sendReaction(content: string, schema?: Reaction["schema"]): Promise<void>

Send a reaction to this message with push disabled.

sendReaction
("👍", "unicode");
}
});
const agent: Agent<unknown>
agent
.
NodeJS.EventEmitter<EventHandlerMap<unknown>>.on<"reply">(eventName: keyof EventEmitterEventMap | "actions" | "attachment" | "conversation" | "group-update" | "dm" | "group" | "inline-attachment" | "intent" | "leave-request" | "markdown" | "message" | "multi-attachment" | "reaction" | "read-receipt" | "reply" | "start" | "stop" | "text" | "transaction-reference" | "unhandledError" | "unknownMessage" | "wallet-send-calls", listener: (ctx: MessageContext<{
readonly kind: "reply";
readonly referenceId: MessageId;
readonly body: MessageBody;
}, unknown>) => void): Agent<...>

Adds the listener function to the end of the listeners array for the event named eventName. No checks are made to see if the listener has already been added. Multiple calls passing the same combination of eventName and listener will result in the listener being added, and called, multiple times.

server.on('connection', (stream) => {
console.log('someone connected!');
});

Returns a reference to the EventEmitter, so that calls can be chained.

By default, event listeners are invoked in the order they are added. The emitter.prependListener() method can be used as an alternative to add the event listener to the beginning of the listeners array.

import { EventEmitter } from 'node:events';
const myEE = new EventEmitter();
myEE.on('foo', () => console.log('a'));
myEE.prependListener('foo', () => console.log('b'));
myEE.emit('foo');
// Prints:
// b
// a

@since ― v0.1.101

@param ― eventName The name of the event.

@param ― listener The callback function

on
("reply", (
ctx: MessageContext<{
readonly kind: "reply";
readonly referenceId: MessageId;
readonly body: MessageBody;
}, unknown>
ctx
) => {
var console: Console
console
.
Console.log(...data: any[]): void

The console.log() static method outputs a message to the console.

MDN Reference

log
(
ctx: MessageContext<{
readonly kind: "reply";
readonly referenceId: MessageId;
readonly body: MessageBody;
}, unknown>
ctx
.
MessageContext<{ readonly kind: "reply"; readonly referenceId: MessageId; readonly body: MessageBody; }, unknown>.content: {
readonly kind: "reply";
readonly referenceId: MessageId;
readonly body: MessageBody;
}

Replace the middleware value without changing the SDK message.

The decoded value selected by the content type.

content
.
referenceId: string
referenceId
,
ctx: MessageContext<{
readonly kind: "reply";
readonly referenceId: MessageId;
readonly body: MessageBody;
}, unknown>
ctx
.
MessageContext<{ readonly kind: "reply"; readonly referenceId: MessageId; readonly body: MessageBody; }, unknown>.content: {
readonly kind: "reply";
readonly referenceId: MessageId;
readonly body: MessageBody;
}

Replace the middleware value without changing the SDK message.

The decoded value selected by the content type.

content
.
body: MessageBody
body
);
});
const
const requireAdmin: AgentMiddleware
requireAdmin
:
type AgentMiddleware<ContentTypes = unknown> = (ctx: MessageContext<unknown, ContentTypes>, next: () => Promise<void> | void) => Promise<void>

Processes a message and calls next to continue the middleware chain.

AgentMiddleware
= async (
ctx: MessageContext<unknown, unknown>
ctx
,
next: () => Promise<void> | void
next
) => {
if (!(await
const filter: {
fromSelf: (message: Message, client: Client) => boolean;
hasContent: (message: Message) => message is DecodedMessageWithContent;
isDM: (conversation: Group | Dm) => conversation is Dm;
isGroup: (conversation: Group | Dm) => conversation is Group;
isGroupAdminAsync: (conversation: Group | Dm, message: Message) => Promise<boolean>;
isGroupSuperAdminAsync: (conversation: Group | Dm, message: Message) => Promise<boolean>;
usesCodec: <T extends AnyContentCodec>(message: Message, codecClass: new () => T) => message is DecodedMessageWithContent<ReturnType<T["decode"]>>;
}

Message and conversation tests for agent middleware.

filter
.
isGroupAdminAsync: (conversation: Group | Dm, message: Message) => Promise<boolean>

Await this check before granting group admin access.

isGroupAdminAsync
(
ctx: MessageContext<unknown, unknown>
ctx
.
ConversationContext<unknown, Conversation>.conversation: Conversation

Return the conversation that triggered this context.

conversation
,
ctx: MessageContext<unknown, unknown>
ctx
.
MessageContext<unknown, unknown>.message: Message

The SDK message, including its tagged content and retained bytes.

message
))) return;
if (
ctx: MessageContext<unknown, unknown>
ctx
.
MessageContext<unknown, unknown>.isText(): this is MessageContext<string, unknown>

Narrow this context to a text message.

isText
()) {
ctx: MessageContext<string, unknown>
ctx
.
MessageContext<string, unknown>.content: string

Replace the middleware value without changing the SDK message.

The decoded value selected by the content type.

content
=
ctx: MessageContext<string, unknown>
ctx
.
MessageContext<string, unknown>.content: string

Replace the middleware value without changing the SDK message.

The decoded value selected by the content type.

content
.
String.trim(): string

Removes the leading and trailing white space and line terminator characters from a string.

trim
();
}
await
next: () => Promise<void> | void
next
();
};
const agent: Agent<unknown>
agent
.
Agent<unknown>.use(...middleware: (AgentMiddleware<unknown> | AgentMiddleware<unknown>[])[]): Agent<unknown>

Add message middleware. Middleware runs in registration order.

use
(
const requireAdmin: AgentMiddleware
requireAdmin
);

Read decoded content from ctx.content after a context type guard narrows its type.

sendReaction(content, schema?), sendTextReply(text), and sendMarkdownReply(markdown) keep their arguments. They now send with shouldPush: false. Use the underlying conversation send methods when the app needs another push option.

The Agent helpers createDmWithAddress, createGroupWithAddresses, addMembersWithAddresses, and getConversationContext keep their names. They now call Node createDm, createGroup, addMembers, and getById. The Node option and result shapes changed. Do not pass old Group<T> or Dm<T> types. Ethereum addresses use viem Hex.

Handles are new. createDmWithHandle, createGroupWithHandles, and addMembersWithHandles take handles such as nick#2341. See Handles.

const
const peer: "0x0000000000000000000000000000000000000001"
peer
= "0x0000000000000000000000000000000000000001";
const
const dm: Dm
dm
= await
const agent: Agent<unknown>
agent
.
Agent<unknown>.createDmWithAddress(address: `0x${string}`, options?: CreateDmOptions): Promise<Dm>

Create a DM with an Ethereum address. The address is converted to an identifier.

createDmWithAddress
(
const peer: "0x0000000000000000000000000000000000000001"
peer
);
const
const group: Group
group
= await
const agent: Agent<unknown>
agent
.
Agent<unknown>.createGroupWithAddresses(addresses: `0x${string}`[], options?: CreateGroupOptions): Promise<Group>

Create a group from Ethereum addresses.

createGroupWithAddresses
([
const peer: "0x0000000000000000000000000000000000000001"
peer
]);
await
const agent: Agent<unknown>
agent
.
Agent<unknown>.addMembersWithAddresses<unknown>(group: Group, addresses: `0x${string}`[]): ReturnType<Group["addMembers"]>

Add Ethereum addresses to an existing group.

addMembersWithAddresses
(
const group: Group
group
, [
"0x0000000000000000000000000000000000000002",
]);
const
const context: ConversationContext<unknown, Conversation> | undefined
context
= await
const agent: Agent<unknown>
agent
.
Agent<unknown>.getConversationContext(conversationId: string): Promise<ConversationContext<unknown, Conversation> | undefined>

Resolve a conversation context, or return undefined when it is not local.

getConversationContext
(
const dm: Dm
dm
.
Dm.id: string
id
);
var console: Console
console
.
Console.log(...data: any[]): void

The console.log() static method outputs a message to the console.

MDN Reference

log
(
const context: ConversationContext<unknown, Conversation> | undefined
context
?.
ClientContext<unknown>.getClientAddress(): string

Return the account identifier used by the client, when available.

getClientAddress
(),
function createIdentifier(user: User): PublicIdentity

Convert a user wallet address to the XMTP identifier shape.

createIdentifier
(
const user: User
user
));
Old attachment APINew attachment API
ctx.sendRemoteAttachment(file, upload)Same call; upload is now optional
createRemoteAttachmentFromFile(file, upload)createRemoteAttachmentFromFile(client, file, upload?)
downloadRemoteAttachment(remote)downloadRemoteAttachment(client, remote)
Callback receives EncryptedAttachmentCallback receives HostedAttachment: encryption keys, encrypted payload, optional filename
createRemoteAttachment(encrypted, url)Same name; input is HostedAttachment, with Rust-generated encryption keys

Without a callback, file creation uses the client’s configured backend attachment service and awaits upload. With a callback, the SDK encrypts the encoded attachment through Rust and passes the ciphertext to app-owned hosting. Download uses the client’s attachment service for URL checks, decryption, digest checks, and local storage. It returns file content, MIME type, and filename. It no longer performs a direct unverified fetch in the Agent utility.

Before:

const remote = await createRemoteAttachmentFromFile(file, upload);
await conversation.sendRemoteAttachment(remote);
const downloaded = await downloadRemoteAttachment(remote);

After, with backend attachment hosting:

const
const file: File
file
= new
var File: new (fileBits: BlobPart[], fileName: string, options?: FilePropertyBag) => File

The File interface provides information about files and allows JavaScript in a web page to access their content.

MDN Reference

File
(["hello"], "hello.txt", {
BlobPropertyBag.type?: string | undefined
type
: "text/plain" });
const
const remote: RemoteAttachment
remote
= await
function createRemoteAttachmentFromFile(client: Client, file: File, uploadCallback?: AttachmentUploadCallback): Promise<RemoteAttachment>

Create and upload a file. The optional callback selects app-owned hosting.

createRemoteAttachmentFromFile
(
const agent: Agent<unknown>
agent
.
Agent<unknown>.client: Client

Return the wrapped client for operations outside agent middleware.

client
,
const file: File
file
);
await
const dm: Dm
dm
.
Dm.sendRemoteAttachment(attachment: RemoteAttachment, options?: SendOptions): Promise<MessageId>
sendRemoteAttachment
(
const remote: RemoteAttachment
remote
);
const
const downloaded: Attachment
downloaded
= await
function downloadRemoteAttachment(client: Client, remoteAttachment: RemoteAttachment): Promise<Attachment>

Download and verify an attachment through Rust.

downloadRemoteAttachment
(
const agent: Agent<unknown>
agent
.
Agent<unknown>.client: Client

Return the wrapped client for operations outside agent middleware.

client
,
const remote: RemoteAttachment
remote
);
var console: Console
console
.
Console.log(...data: any[]): void

The console.log() static method outputs a message to the console.

MDN Reference

log
(
const downloaded: Attachment
downloaded
.
filename?: string | undefined
filename
);

start() and stop() keep their names. start() now opens native conversation and message readers without a separate conversation sync. The start event reports local reader readiness; the backend can be offline. Conversation events exclude groups already stored locally and include a pending Welcome when it first discovers its group.

AgentStreamingOptions and StreamAllMessagesOptions<T> now use Node StreamOptions: signal, onClose, and onConnectionStateChange. Remove old retry settings, disableSync, onFail, and onRestart. Agent start() does not expose a message selection, replay cursor, or onValue option.

Core owns network recovery with a finite budget. When a terminal error ends a reader, the Agent closes both readers before it calls error middleware. The old Agent could restart its streams automatically after a handled streaming error. The new Agent requires an explicit agent.start() to open a new generation, even when error middleware calls next(). A second start() while running has no effect. stop() ends both readers and permits a later start on the same client.

The event names remain: actions, attachment, conversation, group-update, dm, group, inline-attachment, intent, leave-request, markdown, message, multi-attachment, reaction, read-receipt, reply, start, stop, text, transaction-reference, unhandledError, unknownMessage, and wallet-send-calls. Their contexts use the new Node content records. unknownMessage now includes unknown content and custom content without a built-in event. The old Agent skipped messages whose decoded content was absent.

Message middleware still runs in registration order. Normal return stops the middleware chain and accepts the message without events. next() continues to the content event and then message. The Agent skips its own inbox. It now waits for async message and conversation listeners. Await all work that must finish before delivery acceptance. A task that runs without await does not delay acceptance.

Error middleware still uses agent.errors.use(). next() handles the error and resumes processing when possible. next(error) or a thrown error passes that error to the next handler. Returning without next() stops the reader without accepting the failed message. An unhandled message failure has the same effect. A new default reader can deliver the failed message again. Make external actions safe when delivery repeats. Error contexts always have client; message and conversation can be absent.

const agent: Agent<unknown>
agent
.
NodeJS.EventEmitter<EventHandlerMap<unknown>>.on<"unhandledError">(eventName: keyof EventEmitterEventMap | "actions" | "attachment" | "conversation" | "group-update" | "dm" | "group" | "inline-attachment" | "intent" | "leave-request" | "markdown" | "message" | "multi-attachment" | "reaction" | "read-receipt" | "reply" | "start" | "stop" | "text" | "transaction-reference" | "unhandledError" | "unknownMessage" | "wallet-send-calls", listener: (error: Error) => void): Agent<unknown>

Adds the listener function to the end of the listeners array for the event named eventName. No checks are made to see if the listener has already been added. Multiple calls passing the same combination of eventName and listener will result in the listener being added, and called, multiple times.

server.on('connection', (stream) => {
console.log('someone connected!');
});

Returns a reference to the EventEmitter, so that calls can be chained.

By default, event listeners are invoked in the order they are added. The emitter.prependListener() method can be used as an alternative to add the event listener to the beginning of the listeners array.

import { EventEmitter } from 'node:events';
const myEE = new EventEmitter();
myEE.on('foo', () => console.log('a'));
myEE.prependListener('foo', () => console.log('b'));
myEE.emit('foo');
// Prints:
// b
// a

@since ― v0.1.101

@param ― eventName The name of the event.

@param ― listener The callback function

on
("unhandledError", (
error: Error
error
) =>
var console: Console
console
.
Console.error(...data: any[]): void

The console.error() static method outputs a message to the console at the "error" log level. The message is only displayed to the user if the console is configured to display error output. In most cases, the log level is configured within the console UI. The message may be formatted as an error, with red colors and call stack information.

MDN Reference

error
(
error: Error
error
));
const agent: Agent<unknown>
agent
.
Agent<unknown>.errors: AgentErrorRegistrar<unknown>

Return the error-middleware registrar.

errors
.
function use(...errorMiddleware: (AgentErrorMiddleware<unknown> | AgentErrorMiddleware<unknown>[])[]): AgentErrorRegistrar<unknown>

Append one or more error middleware functions to the error chain.

use
((
error: unknown
error
,
_ctx: AgentErrorContext<unknown>
_ctx
,
next: (err?: unknown) => Promise<void> | void
next
) => {
if (
error: unknown
error
instanceof
class AgentStreamingError

Error raised when an Agent SDK stream fails.

AgentStreamingError
) {
var console: Console
console
.
Console.error(...data: any[]): void

The console.error() static method outputs a message to the console at the "error" log level. The message is only displayed to the user if the console is configured to display error output. In most cases, the log level is configured within the console UI. The message may be formatted as an error, with red colors and call stack information.

MDN Reference

error
("Streams ended",
error: AgentStreamingError
error
.
Error.cause?: unknown
cause
);
return;
}
return
next: (err?: unknown) => Promise<void> | void
next
(
error: unknown
error
);
});
await
const agent: Agent<unknown>
agent
.
Agent<unknown>.start(options?: AgentStreamingOptions): Promise<void>

Start conversation and message streams. Calling this while running is a no-op.

start
({
onConnectionStateChange?: ((previous: ConnectionState | undefined, current: ConnectionState) => void) | undefined
onConnectionStateChange
: (
_previous: ConnectionState | undefined
_previous
,
current: ConnectionState
current
) =>
var console: Console
console
.
Console.log(...data: any[]): void

The console.log() static method outputs a message to the console.

MDN Reference

log
(
current: ConnectionState
current
),
onClose?: ((reason: StreamCloseReason) => void) | undefined
onClose
: (
reason: StreamCloseReason
reason
) =>
var console: Console
console
.
Console.log(...data: any[]): void

The console.log() static method outputs a message to the console.

MDN Reference

log
(
reason: StreamCloseReason
reason
.
kind: "failed" | "closed"
kind
),
});
// After the app resolves the cause of a terminal error, it can call start again.
await
const agent: Agent<unknown>
agent
.
Agent<unknown>.stop(): Promise<void>

Stop both streams and emit the stop event. Calling this is safe repeatedly.

stop
();
await
const agent: Agent<unknown>
agent
.
Agent<unknown>.start(options?: AgentStreamingOptions): Promise<void>

Start conversation and message streams. Calling this while running is a no-op.

start
();
await
const environmentAgent: Agent<unknown>
environmentAgent
.
Agent<unknown>.stop(): Promise<void>

Stop both streams and emit the stop event. Calling this is safe repeatedly.

stop
();

CommandRouter keeps its constructor, command, default, handle, middleware, and commandList API. Command arguments now replace ctx.content. They do not replace ctx.message.content.

ActionWizard keeps its builder, session, and handler APIs. Its prompts now disable push. Its handlers read tagged intent content and ctx.content. ActionWizardCompleteHandler and ActionWizardCancelHandler are now exported types.

PerformanceMonitor, its config, and HealthReport keep their API and behavior. Its health timer does not keep the process alive. Empty event-loop samples produce zero delay.

createERC20TransferCalls and createNativeTransferCalls add metadata.extra: new Map() to the wallet-call records they return.

logDetails, getInstallationInfo, getTestUrl, getValidLogLevels, and parseLogLevel remain. Log levels are lower-case strings instead of enum values. InstallationInfo is now exported. Its fields remain totalInstallations, installationId, mostRecentInstallationId, and isMostRecent. Installation details use id and createdAt.ns from the Node records. getTestUrl no longer includes an environment segment. Use a test client configured for the same self-hosted backend when you test delivery.

EventHandlerMap is now exported. Node Client, Group, and Dm no longer take a decoded-content generic. Agent context type parameters remain, but the SDK message is always the tagged Node Message.