Skip to content

Web (@agentimhq/client-web) API

Generated from the source and its doc comments (sdk/client-web/src/client.ts, sdk/client-web/src/view.ts, sdk/client-web/src/store.ts).

Terminal window
npm install @agentimhq/client-web
interface ClientOptions

How the client starts: the server address, this device’s id, the token provider, the cache and how it names itself.

url: string

https://chat.example.com.

deviceId: string

This device’s id, the same every run (1..64 of A-Z a-z 0-9 _ -).

deviceName?: string

A name for this device in the user’s device list, like “Maya’s laptop”.

platform?: string

The platform reported to the server (web, ios, android…), for the device list and push.

tokenProvider: () => Promise<string>

A client token from your backend; throw TokenRefused to stop.

store?: Store | 'indexeddb' | 'memory'

The cache: IndexedDB by default where there is one, else memory.

storeName?: string

Name of the IndexedDB databases (default agentim-<deviceId>).

wasm?: WasmSource

The core module (default: agentim_core.wasm next to this file).

multiTab?: boolean

Share one connection between tabs (default: where the browser can).

WebSocket?: typeof WebSocket

The WebSocket class to use, for Node and tests; browsers have their own.

clientName?: string

This app’s name, sent with each connection (for diagnostics and the server’s minimum-version rules).

clientVersion?: string

This app’s version, sent with clientName.

log?: (m: string) => void

Receives the client’s diagnostic lines (connections, retries, failures); none by default.

class AgentIMClient

The AgentIM client for one account on one device: keeps the connection, syncs, sends the offline queue in order, keeps the local cache, and turns everything into observable state.

readonly connection = new Observable<Connection>({ state: 'closed' })

The connection state (closed, connecting, connected, disconnected, suspended, failed) and why; subscribe to follow it.

readonly conversations = new Observable<any[]>([])

The conversation list, newest first, kept up to date.

static async create(o: ClientOptions): Promise<AgentIMClient>

Starts a client with its options: the server address, this device’s id, the token provider and the cache. Nothing connects until connect. In a browser the first tab holds the connection; other tabs share it.

async command(cmd: Record<string, unknown>, blobs?: Record<string, Blob>): Promise<any>

Any core command ({cmd: …}); resolves with its result.

on(type: string, fn: Listener): () => void

Events: operation, side, signal, reset, storage, event, read, mute.

connect()

Connects and keeps connecting: reconnects with backoff when the network or the server goes, syncs what it missed, sends the offline queue.

close()

Disconnects; the queue and the cache stay. connect again picks up where it stopped.

logout()

Logs out here: closes and wipes the cache and the queue (GDPR).

async dispose()

Stops this tab’s part (the next tab takes over the connection).

messages(convId: string): Messages

The conversation’s messages, kept up to date while subscribed.

async refreshConversations()

Loads the conversation list (it updates by itself afterwards).

async sendFile(convId: string, file: Blob, opts: { name?: string; caption?: string; replyTo?: string; clientMsgId?: string; keepMetadata?: boolean } = {})

Sends a file or an image: shown at once as uploading (with progress), sent when it is up. An image loses its EXIF and GPS (unless keepMetadata) and gets a thumbnail of 320 pixels.

localPreview(key?: string | null): string | undefined

A local address for a file this tab is sending (payload.local_thumb or local_source).

fileUrl(convId: string, fileId: string, variant: 'original' | 'thumb' = 'original'): Promise<{ url: string; variant: string; expires_at: number }>

A link to read a file (original or thumb), valid a few minutes.

send(convId: string, text: string, opts: { kind?: string; payload?: unknown; replyTo?: string; clientMsgId?: string; threadId?: string; alsoInConv?: boolean } = {})

Sends a text (or a custom kind with its payload): shown at once as sending, sent when the connection allows, in order. Returns the client_msg_id and the local key; a retry with the same client_msg_id is the same message.

edit(convId: string, target: { msgId?: string; clientMsgId?: string }, text: string)

Edits one of my messages (by msgId, or by clientMsgId while it is still queued); the change shows at once.

recall(convId: string, target: { msgId?: string; clientMsgId?: string })

Recalls one of my messages for everyone; a message still in the queue is simply not sent.

read(convId: string, seq?: number)

Marks the conversation read up to seq (default: the newest) on all my devices.

feedback(convId: string, msgId: string, rating: 'up' | 'down', reason?: string)

Rates an agent’s message up or down, with an optional reason.

react(convId: string, msgId: string, key: string): Promise<{ changed: boolean; seq?: number }>

Adds a reaction (one emoji, or one of the app’s :names:); adding it twice changes nothing.

unreact(convId: string, msgId: string, key: string): Promise<{ changed: boolean; seq?: number }>

Removes my reaction; removing one that is not there changes nothing.

listReactions(convId: string, msgId: string, key: string, opts: { afterSeq?: number; limit?: number } = {})

Who reacted to a message with a key, oldest first.

pin(convId: string, msgId: string): Promise<{ changed: boolean; seq?: number }>

Pins a message to the conversation; pinning it again changes nothing.

unpin(convId: string, msgId: string): Promise<{ changed: boolean; seq?: number }>

Unpins a message.

listPins(convId: string, opts: { beforeSeq?: number; limit?: number } = {})

The conversation’s pinned messages, latest pin first.

pullThread(convId: string, threadId: string, opts: { afterSeq?: number; limit?: number } = {})

A thread’s root and its replies after afterSeq.

listThreads(opts: { convId?: string; before?: number; limit?: number } = {})

The threads I follow (in one conversation, or all), latest reply first, with unread counts.

readThread(convId: string, threadId: string, seq: number)

Marks a thread read up to seq on all my devices.

followThread(convId: string, threadId: string, follow = true)

Follows a thread (its replies notify me) or stops following it.

msgSummary(convId: string, msgIds: string[])

The thread summary, reactions and pin of up to 100 messages (for messages loaded from history).

respond(convId: string, msgId: string, blockId: string, r: { action?: string; values?: Record<string, unknown> })

Answers a card: a button (action) or a filled form (values).

retry(convId: string, clientMsgId: string)

Sends a failed message again (the same client_msg_id, so it is never doubled).

discard(convId: string, clientMsgId: string)

Drops a failed or waiting message from the queue.

focus(convId: string | null)

The conversation on screen (or none): its agents’ replies stream in live.

pendingOps()

What waits in the offline queue.

call(op: string, args: Record<string, unknown> = {})

A request that needs the server now (approvals, decisions, queries).

debugState()

Everything that helps report a problem: the connection, positions, the queue and recent failures.

interface Commands

What a view needs from its client.

command(cmd: Record<string, unknown>): Promise<any>

Runs one core command and resolves with its answer; the typed methods are built on this.

class TokenRefused

Thrown by a token provider when your backend refuses the user: the client stops instead of retrying.

class AgentIMError

A refused or failed command: code is the error code, retryable whether trying again may work.

code: string

The error code: the server’s (forbidden, not_found, conflict, bad_request, …) or the client’s (disconnected, timeout, closed, unauthenticated).

retryable: boolean

Whether the same request may work later.

retryAfterMs?: number

How long the server asked to wait before trying again.

class Observable

A value the UI follows: get() returns the same object until it changes.

get = (): T => this.v

The current value (the same object until it changes, for React’s useSyncExternalStore).

subscribe = (fn: (v: T) => void): (() => void) =>

Calls fn on every change; returns the function that stops it.

set(v: T)

Replaces the value and tells the subscribers.

interface Connection

The connection state and why.

state: 'closed' | 'connecting' | 'connected' | 'disconnected' | 'suspended' | 'failed'

closed (not started or closed), connecting, connected, disconnected (waiting to reconnect), suspended (offline, or down for a long time: retries less often) or failed (stopped: the token was refused or the account deactivated).

reason?: string | null

Why it is not connected (an error code), when it is not.

retryAt?: number | null

When the next reconnect attempt is due (Unix ms), while waiting to reconnect.

interface Message

One message as shown in a list.

key: string

The list identity: local:<client_msg_id> for a message sent from this device until the server’s copy replaces it (in one change), then its msg_id.

msg_id: string

The server’s id (local:<client_msg_id> until the server has it).

mine?: boolean

Whether I sent it (from any of my devices).

client_msg_id?: string

The id this device gave the message, for retries.

conv_id: string

The conversation it belongs to.

content: string

The text (for a streaming reply, what has arrived so far).

status: 'sent' | 'sending' | 'uploading' | 'failed'

sent, sending (queued or on its way), uploading (its file is uploading) or failed (refused; retry or discard it).

order: number

The sort position in the list.

class Messages

One conversation’s messages as shown: snapshot, then changes.

hasOlder = false

Whether older messages can still be loaded.

olderEnd?: string

Why nothing older can be loaded: start (the beginning) or retention (deleted by the retention period).

reset(list: Message[], hasOlder?: boolean, olderEnd?: string)

@internal

apply(changes: any[])

@internal

applyEvent(e: { reset?: boolean; changes?: any[] })

@internal A messages event: with reset the view is replaced by the changes in one step, so no one sees it empty in between.

async loadOlder(): Promise<boolean>

The page before the oldest shown: the cache first, then the server.

jumpTo(seq: number)

A page around a message (it stays loaded).

interface Batch

One atomic write to the cache: everything in it is written or nothing is.

id: number

The batch number; the core waits for each in order.

convs: Array<{ conv_id: string; [k: string]: unknown }>

Conversations to write (replacing the stored ones).

put_messages: Array<Record<string, any>>

Messages to write.

remove_messages: Array<[string, string]>

Messages to delete, as [conv_id, key].

put_ops: Array<Record<string, any>>

Queued operations to write (the offline queue).

remove_ops: string[]

Queued operations that are done, by id.

meta?: { account?: string | null } | null

Which account the cache belongs to; a different account wipes it.

interface Snapshot

What the cache holds at start: the account, the conversations, the queue and recent messages.

account: string | null

The account the cache belongs to, or null when empty.

convs: unknown[]

The stored conversations.

ops: unknown[]

The stored offline queue, in order.

messages: unknown[]

The newest messages of each conversation, up to the page size.

type Failure = 'full' | 'corrupt' | 'unavailable'

Why a write failed, as the core names it.

class StoreError

Why the cache could not be written: full, corrupt or unavailable.

interface Store

A cache for the client (IndexedDB in browsers, memory elsewhere); implement it to keep the cache somewhere else.

readonly kind: 'indexeddb' | 'memory'

indexeddb or memory.

open(): Promise<void>

Opens the cache; fails with StoreError when it cannot.

snapshot(page: number): Promise<Snapshot>

Reads what the cache holds, with up to page messages per conversation.

page(convId: string, beforeSeq: number, limit: number): Promise<unknown[]>

Reads up to limit older messages of a conversation before beforeSeq.

write(b: Batch): Promise<void>

Writes one batch atomically.

wipe(): Promise<void>

Deletes everything (sign-out, a different account).

persisted?: boolean

navigator.storage.persist() result, when asked.

class MemoryStore

Nothing kept after the page closes.

readonly kind = 'memory'

memory.

async open() {}

Nothing to open.

async snapshot(page: number): Promise<Snapshot>

What the core reads at start: the account, conversations, the queue and the newest page messages of each conversation.

async page(convId: string, before: number, limit: number)

Messages of a conversation before a seq, for paging back.

async write(b: Batch)

Writes a batch from the core atomically.

async wipe()

Deletes everything (logout, another account).

class IndexedDBStore

IndexedDB, one per account and device (name). Asks the browser to keep it (navigator.storage.persist()) unless told not to.

readonly kind = 'indexeddb'

indexeddb.

persisted?: boolean

Whether the browser agreed to keep the cache (navigator.storage.persist()).

async open()

Opens (or creates) the cache and the queue databases and the encryption key.

async snapshot(page: number): Promise<Snapshot>

What the core reads at start: the account, conversations, the queue and the newest page messages of each conversation.

async page(convId: string, before: number, limit: number)

Messages of a conversation before a seq, for paging back.

async write(b: Batch)

Writes a batch from the core in one transaction (the queue in its own database).

async wipe()

Deletes both databases (logout, another account).