Skip to content

Wire Protocol

OperationDescription
appendAdd a new component to the canvas
replaceSwap props on an existing component
removeDelete a component by ID
toastShow a transient notification
navigateTrigger client-side navigation
resetClear all UI state (end-of-conversation, summarizer flush)

For deeply nested or large nodes, agents can emit minimal RFC 6902 JSON Patch deltas instead of full props snapshots:

{
op: "ui.replace",
key: "todo-list",
patch: [
{ op: "replace", path: "/items/3/status", value: "done" }
]
}
  • Paths target the node’s props object (root "" means props itself).
  • All ops are supported: add, remove, replace, move, copy, test.
  • All-or-nothing: any failing op aborts the patch and surfaces via onInvalidEvent.
  • Use full props for simple updates; use patch when the diff is small relative to the node.

Both forms can interleave for the same key.

LLM tool calls stream their JSON args incrementally. parsePartialJson returns a Partial<T> after each delta, repairing truncated input:

import { parsePartialJson, streamingJsonParse } from "@kibadist/agentui-react";
parsePartialJson<{ q: string; tags: string[] }>('{"q":"foo","tags":[1,2');
// → { q: "foo", tags: [1, 2] }
for await (const partial of streamingJsonParse<{ q: string }>(stream)) {
// partial.q updates progressively
}

The reducer uses parsePartialJson internally so state.toolCalls.get(id).args updates after every tool.args-delta event, not only at completion.

Servers can declare available node types, accepted actions, and the session’s effective permissions as the first event of a stream:

// server-side
{
op: "session.init",
capabilities: {
nodeTypes: ["Card", "Quote", "ClientCard"],
actions: ["purchase.confirm", "quote.send"],
permissions: ["quotes.write", "clients.read"],
}
}

Consumers read the declaration via useCapabilities():

import { useCapabilities } from "@kibadist/agentui-react";
function ConfirmButton() {
const caps = useCapabilities();
if (!caps.canAct("purchase.confirm")) return null;
return <button>Confirm</button>;
}

AgentRenderer consults ComponentSpec.requires against permissions. If the session lacks any required permission, the node hides silently — or renders a host-supplied fallback:

<AgentRenderer
state={state}
registry={registry}
permissionFallback={(node, missing) => (
<div>You need {missing.join(", ")} to view this.</div>
)}
/>

Servers that don’t emit session.init see no behavior change — gating only activates after the handshake.

const { state, reset } = useAgentStream({ url, sessionId });
useEffect(() => { reset(); }, [sessionId, reset]); // fresh state on session change

Migrating from a hand-rolled agentNodeOffset workaround: delete the offset bookkeeping and call reset() instead — the reducer now hands back fresh nodes / byKey references on every reset, so there’s nothing to subtract from.

Wire-event types (UIEvent, UIAppendEvent, etc.) are re-exported from @kibadist/agentui-react as of 0.4.0. Consumers that previously dual-depended on @kibadist/agentui-protocol just to type onEvent can drop it:

import type { UIEvent } from "@kibadist/agentui-protocol";
import type { UIEvent } from "@kibadist/agentui-react";