react-websocket/ag-ui

Run AG-UI agents over a WebSocket. The guide is AG-UI over WebSockets.

The entry has no React dependency. useAgUiAgent comes from react-websocket. createAgUiAgent is about 3.5 kB min+gzip, including the runs and feeds it uses, which it shares with the other entries.

createAgUiAgent

function createAgUiAgent<S, In, Out>(
  client: WebSocketClient<In, Out>,
  options?: {
    threadId?: string;
    state?: S;
    name?: string;
    run?: (input: RunAgentInput, runId: string) => Out;
    resume?: (runId: string, after: number | undefined) => Out;
    cancel?: (runId: string) => Out;
    resync?: (runId: string) => Out;
    event?: (message: In) => { runId: string; offset: number; event: AgUiEvent } | null | undefined;
    policy?: 'keep' | 'cancel';
    storage?: RunStorage | null;
  }
): AgUiAgent<S>;
OptionDefault
threadIdThe thread's id, sent in every RunAgentInputA random id
stateThe shared state before the first STATE_SNAPSHOT, sent with the first runundefined
nameNames where streaming runs are remembered across reloads'ag-ui'
runThe message that starts a run{ type: 'run', runId, input }
resumeThe message that asks for a run's events after after{ type: 'resume', runId, after }
cancelThe message that stops a run{ type: 'cancel', runId }
resyncThe message that asks for a STATE_SNAPSHOT, sent once each time a STATE_DELTA fails, until a snapshot arrivesNot sent
eventRecognize a run's event in a server messageMessages shaped { runId, offset, event }
policyWhat happens to a run when its connection drops: 'keep' resumes it, 'cancel' stops it'keep'
storageWhere streaming runs are remembered, or nullsessionStorage

Returns an AgUiAgent<S>:

Member
run(input?, options?)Start a run with the thread so far plus input.messages. input also takes tools, context and forwardedProps; options.policy overrides the default. A streaming run is aborted first. Returns the run id
abort()Stop the streaming run
getSnapshot()The thread, an AgUiThread<S>. The same object until it changes
subscribe(listener)Get notified when the thread changes. Compatible with useSyncExternalStore
runsThe runs underneath
clientThe client
dispose()Stop following runs and release the connection

AgUiThread

interface AgUiThread<S> {
  readonly messages: readonly AgUiMessage[];
  readonly state: S;
  readonly status: 'idle' | 'running' | 'finished' | 'error' | 'aborted';
  readonly step?: string;
  readonly error?: { message: string; code?: string };
  readonly result?: unknown;
  readonly stale: boolean; // a STATE_DELTA failed; waiting for a snapshot
}

AgUiMessage has AG-UI's message fields: id, role, content, and toolCalls (each { id, type: 'function', function: { name, arguments } }) for assistant messages, toolCallId for tool messages, and activityType for activity messages.

useAgUiAgent

function useAgUiAgent<S>(agent: AgUiAgent<S>): AgUiThread<S>;

From react-websocket. The agent's thread, re-rendering on every event.

applyAgUiEvent

function applyAgUiEvent<S>(thread: AgUiThread<S>, event: AgUiEvent): AgUiThread<S>;

Fold one AG-UI event into a thread without changing it. createAgUiAgent uses it to reduce each run. Use it yourself to reduce AG-UI events from another source, or wrap it to handle CUSTOM events:

const reduce = (thread: AgUiThread<Plan>, event: AgUiEvent): AgUiThread<Plan> =>
  event.type === 'CUSTOM' && event.name === 'reset_plan'
    ? { ...thread, state: { tasks: [] } }
    : applyAgUiEvent(thread, event);

A STATE_DELTA that can't be applied returns the thread with stale: true and the state unchanged, and later deltas are skipped until a STATE_SNAPSHOT.

applyPatch

function applyPatch<T>(document: T, operations: readonly JsonPatchOperation[]): T;

Apply a JSON Patch (add, remove, replace, move, copy, test) without changing document: what changes is copied, and the rest is shared. Throws if an operation can't be applied, such as a missing path, an index out of range (an array's length isn't one of its members), a pointer that doesn't start with /, or a failed test, and document stays as it was. A key such as __proto__ is added as a property of its own, as JSON.parse would.