mcpField
R
runtime

runtime

github.com/assistant-ui开发296.3k 次安装1 天前更新
代码仓库

关于 runtime

assistant-ui Runtime 为构建助手聊天界面的开发者提供状态与操作层。它支持由本地运行时管理消息,或接入外部消息存储,并通过统一客户端 API 暴露线程、分支和运行生命周期。

主要功能

  1. 01本地运行时与外部存储模式
  2. 02消息、线程和运行生命周期状态
  3. 03分支、编辑与重新生成
  4. 04响应式状态和事件 Hook
  5. 05框架及协议适配器

适用场景

  1. 01将聊天界面连接到模型适配器
  2. 02使用现有消息存储驱动聊天组件
  3. 03观察线程状态并控制运行

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/assistant-ui/skills --skill runtime

安装后在 Claude Code 中输入/runtime确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
adapters.md
external-store.md
local-runtime.md
runtime-concepts.md
state-hooks.md
types.md
voice.md

详细介绍

assistant-ui Runtime

请务必查阅 assistant-ui.com/llms.txt 以获取最新 API。

Runtime 是聊天 UI 背后的状态与操作层:它管理消息、线程、分支和运行生命周期,所有原语和钩子都通过统一的 AssistantClient 读取它。构建 Runtime 有两种方式。LocalRuntime(useLocalRuntime)会为你管理消息存储;你只需实现一个 ChatModelAdapter.run 函数,分支、编辑和重新生成便可直接使用。ExternalStoreRuntime(useExternalStoreRuntime)则相反:消息由你管理,UI 功能会根据你提供的回调启用。useChatRuntime(@assistant-ui/ai-sdk)等框架适配器,以及 useAssistantTransportRuntime 等协议 Runtime,都基于这两种方式之一构建。挂载到 AssistantRuntimeProvider 下后,所有 Runtime 都通过 useAui、useAuiState 和 useAuiEvent 以相同方式读取和驱动。

参考资料

Runtime 层级

AssistantRuntime
├── ThreadListRuntime (threads)
│   └── ThreadListItemRuntime[] (threadListItem)
└── ThreadRuntime (thread)
    ├── ComposerRuntime (composer)        new-message input
    ├── SuggestionRuntime[] (suggestion)  follow-up prompts, via thread.suggestions
    └── MessageRuntime[] (message)
        ├── ComposerRuntime (composer)    edit-message input
        ├── MessagePartRuntime[] (part)
        ├── ChainOfThoughtRuntime (chainOfThought)
        │   └── MessagePartRuntime[] (part)
        └── AttachmentRuntime[] (attachment)

modelContext 和 tools 独立于线程作用域解析;请参阅 runtime-concepts.md 和 tools 技能。树中的每个节点都可通过 aui.<scope>(命令式)或 useAuiState 选择器中的 s.<scope>(响应式)访问,并自动限定到调用组件的渲染位置。

useAui、useAuiState、useAuiEvent

useAui() 返回当前的 AssistantClient。它不会订阅状态,因此其标识只会在结构发生变化时改变(例如切换线程);请在事件处理器和命令式代码中使用它。

import { useAui } from "@assistant-ui/react";

const aui = useAui();
aui.thread.append({ role: "user", content: [{ type: "text", text: "Hello!" }] });
aui.thread.cancelRun();

useAuiState(selector) 订阅 AssistantState 的一个切片,并且仅在所选值发生变化时重新渲染(使用 Object.is 比较)。选择器会在每次存储更新时运行,因此必须返回原始值或稳定引用,不能返回新建的对象或数组字面量,也不能返回整个状态(这会抛出错误)。

import { useAuiState } from "@assistant-ui/react";

const isRunning = useAuiState((s) => s.thread.isRunning);   // primitive: correct
const messages = useAuiState((s) => s.thread.messages);      // stable array reference: correct

// Wrong: a new object literal every call re-renders on every store update
const bad = useAuiState((s) => ({ isRunning: s.thread.isRunning, text: s.composer.text }));

每个值调用一次 useAuiState(也可以组合多次调用);不要将作用域展开到新对象中来捆绑多个值。

useAuiEvent(nameOrSelector, callback) 会在组件的整个生命周期内订阅。回调在 effect-event shim 中运行,因此无需记忆化引用,也会触发最新的闭包。

import { useAuiEvent } from "@assistant-ui/react";

useAuiEvent("thread.modelContextUpdate", ({ threadId }) => {
  console.log("Model context updated", threadId);
});

作用域访问器是属性

从 0.15 开始,aui.<scope> 是属性,而不是函数调用;调用它(aui.thread())仍可用,但已弃用。作用域上的方法仍需保留括号。

aui.thread.getState();             // property accessor
aui.threads.switchToNewThread();
aui.thread.composer().send();      // composer() is a method of the thread scope
aui.thread.message({ index: 0 });  // selector object, not a bare index

选择不可用的作用域不再抛出错误;aui.message 始终为真值。使用 source 检查其是否可用;当作用域未挂载时,source 为 null:

if (aui.message.source != null) {
  aui.message.reload();
}

source、query 和 name 是保留的访问器属性,不会解析为作用域方法。

AuiConfig 和 AuiProvider

AuiProvider 会为一个子树挂载 AssistantClient。其 config 属性必须通过从 @assistant-ui/react 导入的 AuiConfig({...}) 构建(原始对象字面量会导致类型错误)。在顶层,仅设置 config 就会创建该子树的客户端。嵌套在父级 Provider 下时,必须设置 extends:extends={aui} 会扩展父级客户端,extends={null} 会隔离出一个全新的根(开发环境会强制执行)。AssistantRuntimeProvider 会在内部安装 AuiProvider,并且还接受 config,以便在运行时自身的作用域之外附加额外作用域(例如工具包);使用它,而不是手动在运行时外围接入 AuiProvider。

import { AssistantRuntimeProvider, AuiConfig, AuiProvider, Tools, useAui } from "@assistant-ui/react";

// Runtime root: config attaches extra scopes next to the runtime's own
const config = AuiConfig({ tools: Tools({ toolkit }) });
<AssistantRuntimeProvider runtime={runtime} config={config}>{children}</AssistantRuntimeProvider>;

// Nested scope: extend the parent client
function MessageScope({ children }: { children: React.ReactNode }) {
  const aui = useAui();
  const nested = AuiConfig({ tools: Tools({ toolkit: extraToolkit }) });
  return <AuiProvider extends={aui} config={nested}>{children}</AuiProvider>;
}

// Isolated root: detach from any parent client
const isolated = AuiConfig({});
<AuiProvider extends={null} config={isolated}>{children}</AuiProvider>;

配置是纯数据:可以将它提到模块作用域、在每次渲染时内联构建,或使用 memo 记忆化;Provider 不会依赖配置的标识。挂载后,AuiProvider 上的 ref 会接收生成的客户端。

线程和消息操作

const aui = useAui();
const thread = aui.thread;

thread.append({ role: "user", content: [{ type: "text", text: "Hello" }] });
thread.startRun({ parentId: null });
thread.cancelRun();

const state = thread.getState();   // { messages, isRunning, capabilities, composer, ... }

const message = thread.message({ index: 0 });   // or { id: messageId }
message.reload();
message.switchToBranch({ position: "next" });
message.submitFeedback({ type: "positive" });

const editComposer = message.composer();
editComposer.beginEdit();
editComposer.setText("Updated");
editComposer.send();

事件

大多数事件已弃用,建议改用 useAuiState 推导相同的状态转换;与事件处理器不同,它在首次渲染和重放时都能正确工作。

事件状态
threads.selectionChanged当前可用:每次主线程切换时触发一次,参数为 { threadId, previousThreadId }。挂载时不会针对最初选中的线程触发
thread.modelContextUpdate当前可用:模型上下文位于 Provider 中,而不是状态中,因此没有可从状态推导的等效方式
composer.attachmentAddError当前可用:reason 为 "no-adapter" | "not-accepted" | "adapter-error"
composer.send、composer.attachmentAdd已弃用:观察 composer 的 text / attachments
thread.runStart、thread.runEnd已弃用:观察 s.thread.isRunning 的状态变化
thread.initialize已弃用:观察 s.thread.messages 从空变为非空
threadListItem.switchedTo、threadListItem.switchedAway已弃用:使用 threads.selectionChanged;如有需要,可在每项作用域内按 id 进行筛选

这对已弃用的事件仍会触发并继续有效,直到下一个主版本。threads.selectionChanged 也会在旧事件对未覆盖的情况下触发,例如调用 switchToNewThread(),以及深链接指定的初始线程在挂载后解析完成。

可选作用域

如果某个作用域未挂载,s.optional.<scope> 会返回 undefined,而不是抛出异常。这是在同时渲染于该作用域内外的组件中读取作用域的安全方式。

const partType = useAuiState((s) => s.optional.part?.type);

命令式等效写法为 aui.<scope>.source != null。

能力

const capabilities = useAuiState((s) => s.thread.capabilities);

RuntimeCapabilities(来自 @assistant-ui/core):switchToBranch、switchBranchDuringRun、edit、reload、delete、cancel、refetchThread、unstable_copy、speech、dictation、voice、attachments、feedback、queue。运行时几乎都会根据你提供的内容(回调、适配器)推导这些能力,而不是通过显式选项设置;unstable_copy 是唯一一个可通过 unstable_capabilities 强制关闭的标志,ExternalStoreRuntime 支持此操作。refetchThread 表示 aui.threads.reloadMainThread() 会原地刷新打开的线程,还是会改为重新挂载运行时;详见 runtime-concepts.md。

常见问题

“Cannot read property of undefined”

  • 确保在 AssistantRuntimeProvider 内部调用钩子(或使用配置提供了相应作用域的 AuiProvider)。
  • 对于可能尚未挂载的作用域,读取 s.optional.<scope>,或通过 aui.<scope>.source != null 进行判断。

useAuiState 导致无限次重新渲染

  • 选择器返回了新的对象或数组字面量,包括将作用域展开到其中的情况。使用单独的调用选择基本类型,或返回经过记忆化的引用。

旧版钩子导入解析失败

  • useAssistantRuntime、useThreadRuntime、useThread、useMessage、useComposer、useMessagePart、useAttachment、useThreadListItem 及相关钩子已在 0.15 中移除。完整映射请参阅 state-hooks.md,或使用 update 技能。

状态未更新

  • 使用 useAuiState 的选择器,而不是在渲染时读取 getState();getState() 只会获取一次快照,不会订阅状态变化。

多个会话或对话侧边栏

  • 本技能介绍单个会话的状态和运行时钩子。若要创建、切换、归档会话,以及渲染会话列表,请改用 thread-list 技能。

相关技能

  • thread-list -- 多会话 UI、会话 CRUD、threads.selectionChanged 的使用者
  • tools -- 工具包编写、工具调用 UI、审批关卡和人工工具详解
  • elements -- 为你读取此状态的样式化 Thread 和撰写器组件
  • cloud -- AssistantCloud、托管式持久化和 useChatRuntime({ cloud })
  • streaming -- 协议运行时底层的 DataStream 和 AssistantTransport 线缆协议

评论

支持 Markdown