assistant-ui/skills

streaming

Streaming wire protocols and backend helpers for assistant-ui, built on the assistant-stream package.

查看源码
仓库原始内容

按源仓库内容呈现,保留标题、案例、代码、表格、链接以及原文引用的演示图片。

assistant-ui Streaming

Always consult [assistant-ui.com/llms.txt](https://www.assistant-ui.com/llms.txt) for the latest API.

assistant-stream is the wire layer underneath assistant-ui's chat runtimes. It normalizes every backend into one stream of AssistantStreamChunk values, ships encoders and decoders for three wire formats, and adds a resumable-stream layer on top of any of them. If your backend already speaks the Vercel AI SDK, you rarely touch this package directly (streamText plus toUIMessageStream is enough); reach for it when you write a custom endpoint, need to decode a stream yourself, or want resumable streams.

References

When to use it

Streaming the model call through the Vercel AI SDK?
├─ Yes → streamText + toUIMessageStream/createUIMessageStreamResponse (or result.toUIMessageStreamResponse())
│        assistant-stream is optional: only needed to decode the response yourself or add resumable streams
└─ No → build the response with assistant-stream
    ├─ Emitting message parts (text, reasoning, tool calls) → Data Stream
    └─ Streaming a full agent state snapshot with custom commands → Assistant Transport

Installation

bash
npm install assistant-stream

@assistant-ui/ai-sdk is the current AI SDK integration package (framework neutral); @assistant-ui/react-ai-sdk still re-exports the same API for older installs but new code should import from @assistant-ui/ai-sdk.

Build a custom streaming response

createAssistantStreamResponse runs a callback with an AssistantStreamController and returns a Response encoded as Data Stream (see data-stream.md for the alternative encoders).

ts
import { createAssistantStreamResponse } from "assistant-stream";

export async function POST(req: Request) {
  return createAssistantStreamResponse(async (controller) => {
    controller.appendText("Hello ");
    controller.appendText("world!");

    controller.appendReasoning("Checking the forecast first.", {
      unstable_summary: "Looking up the weather",
    });

    controller.appendSource({
      type: "source",
      sourceType: "url",
      id: "s1",
      url: "https://example.com/forecast",
      title: "Forecast",
    });

    const tool = controller.addToolCallPart({ toolName: "get_weather" });
    tool.argsText.append('{"city":"NYC"}');
    tool.argsText.close();
    tool.setResponse({ result: { temperature: 22 } });

    controller.close();
  });
}

close() closes any part still open and ends the stream; an uncaught throw inside the callback is turned into an error chunk automatically.

AssistantStreamController

Every server-side stream, whichever encoder ends up wrapping it, is written through this controller (createAssistantStream, createAssistantStreamController, and createAssistantStreamResponse all hand you one).

MethodSignatureNotes
appendText(textDelta: string) => voidOpens a text part on first call, appends to it on the next
appendReasoning(reasoningDelta: string, options?: { unstable_summary?: string }) => voidPassing options always opens a new part, so a summary lands on a part of its own
appendSource(part: SourcePart) => voidSourcePart is { type: "source", sourceType: "url", id, url, title?, parentId? }
appendFile(part: FilePart) => voidFilePart is { type: "file", data, mimeType, parentId? }
appendData(part: DataPart) => voidDataPart is { type: "data", name, data, parentId? }, a named app-defined part
addTextPart() => TextStreamControllerExplicit { append(text), close() } writer, for interleaving with other parts
addReasoningPart(options?) => TextStreamControllerSame writer shape as addTextPart
addToolCallPart(toolName: string) => ToolCallStreamControllerGenerates a toolCallId; see the object overload below for a stable id
addToolCallPart(init: ToolCallPartInit) => ToolCallStreamController{ toolCallId?, toolName, argsText?, args?, response? }
enqueue(chunk: AssistantStreamChunk) => voidRaw escape hatch; prefer the helpers above
merge(stream: AssistantStream) => voidSplices another AssistantStream's parts into this one
withParentId(parentId: string) => AssistantStreamControllerReturns a controller whose writes attach parentId (nested or related parts)
close() => voidCloses the open part, then the stream

addToolCallPart returns a ToolCallStreamController: { argsText: TextStreamController, setResponse(response), close() }. setResponse takes { result, artifact?, isError?, modelContent?, messages? } (the shape returned by a ToolResponse), closes the part automatically, and ignores a second call.

Stream events and part types

Every decoder, regardless of wire format, yields the same normalized AssistantStreamChunk union ({ path: number[] } & { type, ... }):

typeExtra fields
part-startpart: PartInit (see below)
part-finishnone
tool-call-args-text-finishnone
text-deltatextDelta: string
annotationsannotations: ReadonlyJSONValue[]
datadata: ReadonlyJSONValue[]
step-startmessageId: string
step-finishfinishReason, usage: { inputTokens, outputTokens }, isContinued: boolean
message-finishfinishReason, usage
resultresult, isError: boolean, artifact?, modelContent?, messages?
error`error: string, code?, severity?: "critical" \"warning" \"info"`
update-stateoperations: AssistantTransportStateOperation[] (see assistant-transport.md)

PartInit (the part field of part-start) is one of six part types, every variant carrying an optional parentId:

typeExtra fields
textnone
reasoningunstable_summary?: string
tool-calltoolCallId: string, toolName: string
sourcesourceType: "url", id, url, title?
filedata: string, mimeType: string
dataname: string, data: ReadonlyJSONValue

Common Gotchas

`appendSource`, `appendFile`, or `appendData` silently drops the part

  • Pass the full part object including its type field ("source", "file", or "data"); the method name does not imply it for you.

A tool call never settles in the UI

  • addToolCallPart needs a toolName; the id is generated for you unless you pass one. Close argsText (or call setResponse, which closes it for you) or the part never finishes. Register the rendering with a "use generative" toolkit, not the deprecated makeAssistantToolUI; see tools.

Two separate reasoning parts merge into one on the client

  • On the Data Stream wire, a reasoning part-start frame is only sent when unstable_summary is set; a plain appendReasoning(text) call travels only as text deltas, and the decoder has nothing else to tell it a new part started. Opening two summary-less reasoning parts back to back (for example around a tool call) reconstructs as one continuous reasoning part on the client. Give each part a unstable_summary (even an empty-feeling one) or route the tool call through a separate message step to keep them distinct.

Stream not updating the UI

  • Check the Content-Type against the encoder you actually used: DataStreamEncoder (the createAssistantStreamResponse default) sends text/plain; charset=utf-8 with x-vercel-ai-data-stream: v1, not text/event-stream. AssistantTransportEncoder and the AI SDK's UI message stream do send text/event-stream.

Decoder throws "Stream ended abruptly without receiving [DONE] marker"

  • AssistantTransportDecoder and UIMessageStreamDecoder require the terminal [DONE] sentinel; a proxy, CDN, or middleware that buffers or truncates the body breaks this. DataStreamDecoder has no such marker.

`createAssistantStreamResponse` always encodes as Data Stream

  • It hard-codes DataStreamEncoder. For a different wire format, encode manually: AssistantStream.toResponse(createAssistantStream(callback), new AssistantTransportEncoder()), or use createAssistantStreamController and encode the returned stream yourself.

Related Skills

  • runtime -- useLocalRuntime, useExternalStoreRuntime, and the useAssistantTransportRuntime React hook and state hooks
  • setup -- scaffolding an AI SDK route handler and useChatRuntime
  • tools -- "use generative" toolkits and tool-call rendering
  • cloud -- persisting streamed threads and messages with assistant-cloud
来自同一仓库

更多 Skills

全部 Skills
assistant-ui
社区

react-native

Build assistant-ui chat experiences for Expo and bare React Native with @assistant-ui/react-native and @assistant-ui/ai-sdk. Use when setting up an absolute mobile chat endpoint, AssistantRuntimeProvider, useChatRuntime, AssistantChatTransport, native ThreadPrimitive, ComposerPrimitive, MessagePrimitive, thread lists, attachments, React Native styles, Metro withAui for a \"use generative\" toolkit, local or remote thread adapters, or migrating a web chat surface to iOS and Android. Route here wh

安装量
1
GitHub Stars
29
最近更新
10月13日
assistant-ui
社区

update

Upgrades an existing assistant-ui application and applies the migrations needed to reach the current AI SDK v7 and assistant-ui 0.15.x lines. Use when updating, bumping, or migrating @assistant-ui/react, @assistant-ui/ai-sdk, the older @assistant-ui/react-ai-sdk alias, ai, or @ai-sdk/react, or when an upgrade exposes removed hooks, scope accessor calls, provider configuration, registry paths, event names, legacy interactables, or tool registrations. Start here for an existing project, post-updat

安装量
1
GitHub Stars
29
最近更新
10月13日
assistant-ui
社区

assistant-ui

Overview and router for assistant-ui, the React library for building AI chat interfaces from composable primitives and a styled elements catalog. Use for high-level, cross-cutting, or architecture questions: choosing packages, picking a runtime, or understanding the layers (elements, primitives, the aui client with AuiConfig and AuiProvider, the runtime, adapters) and the message model. Covers @assistant-ui/react 0.15.x, @assistant-ui/ai-sdk for AI SDK v7 (useChatRuntime), @assistant-ui/core, @a

安装量
2
GitHub Stars
29
最近更新
10月3日
assistant-ui
社区

primitives

Builds and customizes assistant-ui chat UI from composable, unstyled @assistant-ui/react primitives: ThreadPrimitive, ComposerPrimitive (with UnstableTriggerPopover for mentions and slash commands), MessagePrimitive and MessagePartPrimitive, ActionBarPrimitive, BranchPickerPrimitive, AttachmentPrimitive, ErrorPrimitive, AssistantModalPrimitive, ChainOfThoughtPrimitive, SelectionToolbarPrimitive, SuggestionPrimitive, QueueItemPrimitive, and the ThreadList primitives. Use when assembling or stylin

安装量
2
GitHub Stars
29
最近更新
10月3日