@b4run/ag-ui

中文文档
0.15.8发布于 2026/10/31MITTypeScriptES Module构建来源已验证近 30 天下载 4365

AG-UI protocol adapters for streaming B4.run agent runs to compatible clients.

原始使用文档
文档目录14 节
  1. Install
  2. Example
  3. Streamed tool-call arguments
  4. Model message framing
  5. HTTP client
  6. Activity components
  7. CopilotKit connector
  8. Angular activity kit
  9. Styling
  10. Framework-free view
  11. Runtime and stability
  12. Related
  13. Maturity and support
  14. License

B4.run

@b4run/ag-ui

Supported AG-UI protocol translation for B4.run runtime streams, client inputs, interrupts, activities, and SSE responses.

Use this when: You are translating B4.run runs or activity snapshots for an AG-UI client.

Install

pnpm add @b4run/ag-ui

Requires @ag-ui/core 1.0.2 and @b4run/sdk (the matching B4.run release, for the shared content-part types), both dependencies. @ag-ui/client >=1.0.1 <2.0.0 is an optional peer.

Example

import { fromRunAgentInput, toAguiEvents } from "@b4run/ag-ui"
import { agUiContentType, encodeAgUiEvent } from "@b4run/ag-ui/sse"

const b4Input = fromRunAgentInput(runAgentInput)
const accept = request.headers.accept

response.writeHead(200, { "content-type": agUiContentType(accept) })
for await (const event of toAguiEvents(b4Chunks, { threadId, runId })) {
  response.write(encodeAgUiEvent(event, accept))
}

accept selects the AG-UI HTTP binding: SSE unless the header admits application/vnd.ag-ui.event+proto with a positive quality (named, or through a wildcard such as */*), then 4-byte big-endian length-prefixed protobuf frames. A client that cannot read protobuf names text/event-stream; @ag-ui/client and CopilotKit do.

Plan activity snapshots are translated on the root surface; use the focused API reference for their exact identifiers and payload contracts. Subagents are presented with AG-UI 1.0's SUBAGENT_STARTED/FINISHED/ERROR events, and everything a child does is emitted as the ordinary events for those things tagged with its subagentRunId.

Built-in planning is presented once. A writeTodos call whose plan activity was emitted produces no TOOL_CALL_* events, correlated by the model's tool-call id; every other tool, task included, is unchanged. The rule fails open, so the ordinary tool events are preserved whenever the activity cannot be produced. A client that registers no activity renderer therefore sees less for writeTodos: the activity snapshot is the canonical surface for it.

Streamed tool-call arguments

A tool_call_args chunk with data: { id, name, delta } carries one fragment of a root tool call's arguments ahead of its tool_call announce. The first fragment for an id opens the call with TOOL_CALL_START; each non-empty fragment is one TOOL_CALL_ARGS delta; the announce, which still carries the complete input, emits whatever the deltas did not cover and then TOOL_CALL_END. The deltas concatenate to exactly the single delta a non-streamed call carries, so a client that appends them verbatim sees the same argument JSON either way. A tool_call with no preceding fragments takes the single-delta path unchanged. Run completion, interruption and errors end any streamed call still open. The two orchestration tools never stream.

Model message framing

Model tokens can include messageId alongside their string data. A message_end chunk with data: { messageId } ends that source message. The translator allocates an AG-UI message ID for each source and routes interleaved content independently, including nested model calls inside concurrent tools. An empty model produces no text message. Run completion, interruption, and errors close any remaining open messages.

Every TOOL_CALL_START carries parentMessageId: the AG-UI id of the model message that announced the call. A tool_call (or tool_call_args) chunk names its model invocation as data.messageId, the identity its tokens carry, so the call's parent is the id that invocation's text uses, or a fresh id when the invocation only called tools. A client that builds its message list from events then holds an invocation's text and calls in one assistant message, and has an assistant message for a tool-only phase. Calls without an identity join the anonymous text just before them, until a result ends the model turn. eventsFromState files replayed calls the same way, under the checkpointed AIMessage's id.

Anonymous tokens retain their implicit boundaries at tool events and run end. The CLI carries identity through in-process chunks, NDJSON, and live-turn snapshots. Raw Agent Protocol SSE retains its existing string chunk payload; use AG-UI when consuming independently framed model messages.

Message identity does not deduplicate work repeated by graph checkpoint replay. Complete generation in a separate tool step before an approval-gated operation when the generated response must not repeat on resume.

HTTP client

@b4run/ag-ui/client exports B4HttpAgent, an @ag-ui/client HttpAgent whose getCapabilities() reads GET /agui/{routeId}, the route's AG-UI capabilities, with the agent's own URL, headers and fetch. Register it wherever you would register an HttpAgent; CopilotKit's runtime reports what it returns from /info. It throws on a non-2xx answer. @ag-ui/client is an optional peer dependency, needed only for this subpath.

import { B4HttpAgent } from "@b4run/ag-ui/client"

const agent = new B4HttpAgent({ url: "http://127.0.0.1:3001/agui/%2Fchat%23agent" })

responseSchema is a JSON Schema the route's final answer must match: the agent sends it on every run as forwardedProps.responseSchema, and B4.run binds it on the route's model as the provider's structured output. Tool-calling turns are unaffected. The route must allow the key in server.agui.clientForwardedProps.

forwardIdentity({ headers, resolve, fetch? }) returns a fetch for a server-side client that talks to B4.run on a browser's behalf, such as a CopilotKit runtime route. On every call it deletes the declared headers from whatever the call carried (CopilotKit copies the browser's authorization and x-* headers onto a run, so a browser could send them itself), then sets the values resolve() returns for the current request, or none when it returns undefined. resolve runs per call, because the agent and runner are shared across requests: read the caller from request-scoped state such as an AsyncLocalStorage. Pass the same fetch to B4HttpAgent and to createB4AgentRunner, so runs and thread replay both carry it. It is exported from ./client and ./copilotkit-runtime.

import { B4HttpAgent, forwardIdentity } from "@b4run/ag-ui/client"

const fetch = forwardIdentity({
  headers: ["x-b4-visitor"],
  resolve: () => {
    const visitorId = visitorContext.getStore()?.visitorId
    return visitorId === undefined ? undefined : { "x-b4-visitor": visitorId }
  },
})
const agent = new B4HttpAgent({ url, fetch, responseSchema })

Activity components

@b4run/ag-ui/react is the React activity kit: TurnActivity renders one turn in plain language (the summary line and the step list, nested for subagents), ApprovalCard renders a parked interrupt, and the step rows (Step, StepGroup, PlanStep, ReasoningStep, SubagentStep) and building blocks (Disclosure, StepIcon, StatusText, Checklist) are exported for custom steps. Components take plain props built by @b4run/ag-ui/view (reduceTurns), and the entry has no CopilotKit dependency. In a CopilotKit chat, the connector below wires it up:

import "@b4run/ag-ui/styles.css"
import { B4Activity, useB4ChatSlots } from "@b4run/ag-ui/react/copilotkit"
import { CopilotChat, CopilotKit } from "@copilotkit/react-core/v2"

function Chat() {
  return <CopilotChat {...useB4ChatSlots()} />
}

export default function Page() {
  return (
    <CopilotKit runtimeUrl="/api/copilotkit" useSingleEndpoint={false}>
      <B4Activity>
        <Chat />
      </B4Activity>
    </CopilotKit>
  )
}

CopilotKit connector

@b4run/ag-ui/react/copilotkit is the only entry that imports @copilotkit/react-core; it needs a bundler (CopilotKit's bundle imports its own CSS), so import it from a bundled React app, not from Node or an edge runtime.

  • B4Activity wraps your chat: it hides CopilotKit's generic tool rows, renders one ApprovalCard per parked interrupt, and keeps the thread's turns current from the agent's events; labels, hiddenTools and renderStep reword, hide or re-render steps per tool.
  • renderInChat (default true) lets CopilotKit place the approval cards inside <CopilotChat>, after the messages; renderInChat={false} renders them after B4Activity's children instead, for a host with its own transcript.
  • useB4ActivityContext() returns the turns, labels, renderStep and clock B4Activity provides, for host UI outside the chat such as a map or a side sheet; it throws outside B4Activity.
  • useB4ChatSlots() returns the props to spread onto <CopilotChat>: one row per turn rendered as TurnActivity (the turn's first assistant message with tool calls, holding every call of the turn, after its text when it has some), no toolbar under tool-only rows, and no separate reasoning row for a turn whose TurnActivity already shows that reasoning.
  • mergeTurnMessages is the transformMessages function useB4ChatSlots() installs: it keeps the turn's first assistant message that carries tool calls, text or not, as the activity row and moves every later call of the turn onto it. A later calling message keeps its text without its calls, a later tool-only message is dropped, and subagent messages are dropped (their text lives in the nested turn).
  • useB4Turns() is the agent's thread as turns (reduceTurns) plus markResuming() to call before sending a resume and clearResuming() to forget it when the resume request failed, for a host with its own transcript.

B4Activity owns parts of CopilotKit that have one slot each:

  • CopilotKit has one interrupt slot. Another useInterrupt with the default renderInChat anywhere in the same app replaces B4.run's approval cards.
  • Wildcard useRenderTool({ name: "*" }) registrations are last-wins and never removed, so do not also call useDefaultRenderTool().
  • Approvals support approvals.grants: "off" only. CopilotKit's resolve(payload) carries only the decision, so the interrupt's grant is never echoed on resume; with "optional" or "required" the server answers the resume with 409.

react and @copilotkit/react-core (>=1.76.0) are optional peer dependencies used only by the ./react and ./react/copilotkit subpaths; only ./react/copilotkit imports CopilotKit. Importing the root or ./sse entry never loads them, so a server-only consumer installs nothing extra. The floor tracks the wire protocol: 1.76.0 is the first @copilotkit/react-core whose bundled AG-UI client speaks 1.0, the protocol B4.run serves, and earlier releases resolve a pre-1.0 @ag-ui/* (0.0.59 on 1.70–1.75). pnpm warns on an unmet optional peer; npm 7+ rejects it with ERESOLVE.

Angular activity kit

@b4run/ag-ui/angular is the same kit for Angular 22 or later: standalone, OnPush components with signal inputs (<b4-turn-activity>, <b4-approval-card>, the step rows such as <li b4-step>, and the building blocks) that render the same DOM contract as the React kit, from the same @b4run/ag-ui/view values, styled by the same sheet. Two connectors place them in a chat:

  • @b4run/ag-ui/angular/events takes any AG-UI event stream (an RxJS Observable<BaseEvent>, or anything with the same subscribe) with no CopilotKit — your own transcript, or another chat framework's per-message slot: provideB4Turns folds it into a B4TurnsStore, <b4-message-activity> renders a turn's activity on its first assistant message (messages carry their tool calls as toolCalls: { id }[] or toolCallIds: string[]), and <b4-approvals> renders the parked interrupts' approval cards. toResumeEntries(decisions, interrupts) from @b4run/ag-ui/view turns the cards' decisions into the run's resume once every parked interrupt is decided, echoing each grant at metadata.grant:

    const parked = pendingApprovals(store.turns(), store.labels).map((card) => card.approval)
    const result = toResumeEntries(decisions, parked)
    if (result.ok) await agent.runAgent({ resume: result.entries })
    
  • @b4run/ag-ui/angular/copilotkit drives CopilotKit's <copilot-chat> (@copilotkit/angular >=0.5.3, the only Angular entry that imports it): provideB4Activity follows the chat's agent into turns, and B4ActivityAssistantMessageComponent, B4ActivityReasoningMessageComponent and B4ActivityApprovalsComponent go in the chat's [assistantMessageComponent], [reasoningMessageComponent] and [messageViewChildrenComponent]. The reasoning message renders CopilotKit's own row except for reasoning the turn's activity already shows, as React's useB4ChatSlots does.

import { Component, inject } from "@angular/core"
import { CopilotChat } from "@copilotkit/angular"
import {
  B4ActivityApprovalsComponent,
  B4ActivityAssistantMessageComponent,
  B4ActivityReasoningMessageComponent,
  B4ActivityStore,
  provideB4Activity,
} from "@b4run/ag-ui/angular/copilotkit"

@Component({
  selector: "app-chat",
  imports: [CopilotChat],
  providers: [provideB4Activity({ agentId: "default" })],
  template: `<copilot-chat agentId="default"
    [assistantMessageComponent]="assistant"
    [reasoningMessageComponent]="reasoning"
    [messageViewChildrenComponent]="approvals" />`,
})
export class ChatComponent {
  readonly activity = inject(B4ActivityStore) // created with the chat, so it sees every event
  readonly assistant = B4ActivityAssistantMessageComponent
  readonly reasoning = B4ActivityReasoningMessageComponent
  readonly approvals = B4ActivityApprovalsComponent
}

Load the sheet once, for example in angular.json's styles ("@b4run/ag-ui/styles.css"). @angular/core, @angular/common, @angular/platform-browser (^22.0.0) and @copilotkit/angular are optional peer dependencies; the other entries never load them. The Angular entries ship in Angular's partial compilation format, which your Angular CLI build links.

Styling

The kit ships with B4.run's visual identity via an optional stylesheet. Every rule sits in @layer b4-activity, so any unlayered app CSS wins without specificity games. Without the stylesheet, the components render structured, unstyled markup.

import "@b4run/ag-ui/styles.css"

To restyle, override the design tokens in your own CSS:

:root {
  --b4-activity-radius-card: 8px;
  --b4-activity-font-mono: "JetBrains Mono", monospace;
}

Palette tokens are the one case worth care. Your :root block wins in dark mode too, so a single hard-coded colour applies to BOTH themes — pick values that work in each, or scope them the way the sheet does, on the host's dark selectors:

:root {
  --b4-activity-running: #6d28d9;
}

.dark,
[data-theme="dark"],
:root[data-b4-theme="dark"] {
  --b4-activity-running: #a78bfa;
}

The design tokens are --b4-activity- plus surface, surface-alt, border, text, muted, running, running-bg, complete, failed, failed-bg, primary, on-primary, radius, radius-card, radius-pill, and font-mono.

Put the overrides in plain, unlayered CSS. A Tailwind @theme block is not a substitute: token values declared there lose to this sheet.

Light and dark values ship out of the box. Dark follows the host, not the OS: a .dark or [data-theme="dark"] ancestor selects it; set data-b4-theme="dark" or data-b4-theme="light" on the root element to force one, or data-b4-theme="auto" to follow prefers-color-scheme. All three of the sheet's token blocks are wrapped in :where(), so they carry no specificity at all and your own :root block wins in every theme, whichever sheet the browser parses first.

Beyond tokens, the kit is markup with stable classes (.b4-turn, .b4-step, .b4-approval and their __ parts) and data-state/data-kind attributes, so unlayered app CSS can target any part. For a step whose detail needs its own view, pass renderStep (per tool) to B4Activity or TurnActivity; for a wholly different presentation, build on reduceTurns from ./view and the blocks ./react exports.

Framework-free view

@b4run/ag-ui/view is the half of the client with no React: reduceTurns(view, event) folds AG-UI events into the turns of a thread — tool steps with their b4.step labels, the plan, reasoning, nested subagent turns, and approvals attached to the calls they gate — and stepLabel/groupSteps turn steps into sentences. ./react and ./angular build on it; so can any other framework.

import { EMPTY_TURNS, reduceTurns } from "@b4run/ag-ui/view"

let view = EMPTY_TURNS
for (const event of events) view = reduceTurns(view, event)

Runtime and stability

  • @b4run/ag-ui is a supported, edge-safe integration surface.
  • @b4run/ag-ui/sse is a supported, edge-safe integration surface.
  • @b4run/ag-ui/client is a supported, edge-safe integration surface.
  • @b4run/ag-ui/view is a supported, edge-safe integration surface with no React dependency.
  • @b4run/ag-ui/react is a supported React application surface, built for browser bundles. B4.run records its runtime as node-only, which means only that it does not pass B4.run's edge-safety guard — not that it requires Node: React's own JSX runtime reads process.env.NODE_ENV, which an application bundler substitutes as usual but the stricter edge guard rejects. The other entries never load it.
  • @b4run/ag-ui/react/copilotkit is a supported React application surface recorded as browser-only: it imports CopilotKit, whose bundle imports its own CSS, so it needs a bundler. Browser bundles only: import it from a bundled React app, not from Node or an edge runtime. The other entries never load it.
  • @b4run/ag-ui/angular, @b4run/ag-ui/angular/events and @b4run/ag-ui/angular/copilotkit are supported Angular application surfaces recorded as browser-only: they ship in Angular's partial compilation format, which an Angular build's linker finishes (a plain Node import fails for want of Angular's JIT compiler). Import them from an Angular application built with the Angular CLI. The other entries never load them.
  • @b4run/ag-ui/copilotkit-runtime is a supported application surface recorded as edge-safe: createB4AgentRunner(InMemoryAgentRunner, options) extends the CopilotKit runner class the route passes in (the entry never imports @copilotkit/runtime; it imports only rxjs, a regular dependency, and uses fetch), so it runs wherever your CopilotKit runtime route runs, Node or an edge runtime. It belongs on the server, beside that route. The other entries never load it.
  • @b4run/ag-ui/styles.css is a supported integration surface carrying the default appearance of both kits. It is a stylesheet asset, so it has no runtime classification at all: a bundler resolves it and nothing evaluates it as JavaScript. Import it once alongside your global CSS; it is optional, and every rule that styles an element is scoped to the b4-activity prefix (the sheet also declares --b4-activity-* custom properties on :root, which is intended and harmless — each of those three blocks is wrapped in :where(), so an application's own :root override always wins).

They translate protocol data; they do not authenticate callers or make client-provided state authoritative.

Maturity and support

B4.run is pre-1.0, and its public surface can change. All publishable B4.run packages release together as a fixed group; review the @b4run/ag-ui changelog and upgrading guide before upgrading. For support, use GitHub Discussions; report defects in GitHub Issues.

License

MIT. See the repository license.

版本与发布记录

@b4run/ag-ui 共发布 23 个版本,首次发布于 2026/09/09;过去 12 个月发布了 23 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.15.8latest2026/10/317 天前
0.15.72026/10/2810 天前
0.15.52026/10/2712 天前
0.15.42026/10/2612 天前
0.15.22026/10/2514 天前
0.15.12026/10/2414 天前

关于 @b4run/ag-ui 的常见问题

@b4run/ag-ui 是什么?

AG-UI protocol adapters for streaming B4.run agent runs to compatible clients.

如何安装 @b4run/ag-ui?

在项目目录运行 npm install @b4run/ag-ui,或使用 pnpm add / yarn add / bun add。

@b4run/ag-ui 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@b4run/ag-ui 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@b4run/ag-ui 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=24.0.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@b4run/ag-ui 可以商用吗?

@b4run/ag-ui 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@b4run/ag-ui 的下载量有多少?

根据 npm 官方下载统计,@b4run/ag-ui 在 2026/10/08 – 2026/11/06 这 30 天内被下载 4,365 次,日均约 146 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@b4run/ag-ui 还在维护吗?

最新版本 0.15.8 发布于 2026/10/31(7 天前);至今共发布 23 个版本,其中过去 12 个月发布了 23 个;首次发布于 2026/09/09。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。