跳到内容
BeeuniBeeuni
中文
Esc
↑↓导航↵打开⌘J预览
本页内容

React Hooks

使用 useSession Hook 在 React 中运行 Beeuni 会话。

@beeuni/sdk/react 在 SDK 之上添加了 React hooks。本页介绍 useSession(端到端运行会话的 hook),以及其他已确认可供 React 应用稳定使用的 hooks。

useSession(projectId, sessionId, options?)

useSession 启动会话,打开服务器选定的事件传输通道,并 同步消息、状态和待处理提示。每个会话视图调用一次。

import { useSession } from '@beeuni/sdk/react';

function Chat({ projectId, sessionId }: { projectId: string; sessionId: string }) {
  const s = useSession(projectId, sessionId);

  if (s.phase !== 'ready') return <Booting stage={s.stage} onRetry={s.retry} />;

  return (
    <>
      {s.messages.map(({ info, parts }) => (
        <Message key={info.id} info={info} parts={parts} />
      ))}
      <Composer busy={s.isBusy} onSend={s.send} onStop={s.cancel} />
    </>
  );
}

就绪状态以服务器为准。当 POST /start 返回 stage: 'ready' 时,运行时即已就绪。useSession 不会在客户端另行执行健康检查。

返回值

字段 类型 内容
phase 'starting' | 'ready' | 'error' 整体状态。在 ready 之前显示启动界面。
messages { info, parts }[] 消息列表。各部分实时流式传入。
savedTranscript 'loading' | 'shown' | 'none' 已保存的对话是否能在计算机唤醒前显示。loading:已保存的副本正在传入。shown:消息已在列表中。none:运行时响应前无法显示任何内容。
conversationEmpty boolean 已保存的副本证实对话为空,此后没有回合结束,且没有正在进行或排队的内容。显示消息输入区,而不是启动界面。
status SessionStatus 会话状态。
isBusy boolean 智能体正在生成回复。
questions, permissions array 待处理的智能体问题和工具审批请求。此处的 permission 是一次运行时工具审批,而不是 IAM 权限。
diffs, todos array 实时文件差异和待办事项。
sendError BeeuniSendError | null 最近一次 send 失败:billing、runtime-not-ready 或 runtime-error。
rewindMessageId string | null 可逆回退暂存期间选中的用户消息。
rewindPending boolean 回退或恢复请求正在进行。
rewindError BeeuniSendError | null 最近一次回退或恢复失败。
models, agents, defaultAgent, commands — 可选模型、可选智能体、默认智能体和斜杠命令。运行时启动前即可使用。
retry () => void 强制重新检查 /start。

操作

操作 功能
send(text, override?) 发送提示。override 仅为此消息设置 { model?, agent? }。
sendParts(parts, override?) 通过选定的传输通道发送文本和运行时 URL 文件部分。私有暂存句柄使用平台提示路由。
rewind(messageId) 将此规范会话回退到一条用户消息。选中的消息及后续路径会被隐藏,但可恢复。
restoreRewind() 在另一条提示提交替代路径之前,恢复已移除的路径。
cancel() 停止当前运行并清除待处理的问题和权限请求。先暂停会话中排队的提示,避免排队的提示启动新的运行。
runCommand(command, args) 执行项目斜杠命令。
answerQuestion(id, answers) 回答待处理的智能体问题。
rejectQuestion(id) 拒绝待处理的智能体问题。
answerPermission(id, reply, message?) 回应工具审批请求。reply 为 'once'、'always' 或 'reject'。

useSession 还会返回 removeQuestion 和 removePermission。不要使用 它们。它们会从本地状态中清除提示,却不会通知智能体,因此 运行仍会阻塞。请改用 answerQuestion、rejectQuestion 或 answerPermission。

选项

选项 默认值 功能
waitMs 15000 发送给 /start 的长轮询时间预算。
replayStartStash true 会话就绪后,重放在会话创建之前保存的提示。
enabled true 设置 false 以延迟 hook,例如等到账单检查通过。
chatEngine true 如果应用为此会话挂载了自己的聊天界面,请设置 false,避免重复同步消息。
initialRuntimeSessionId null 服务器已为此会话持有的运行时会话固定引用。在 /start 运行期间,对话记录根据此引用渲染;仍以 /start 的响应为准。initialOpenCodeSessionId 是其已弃用的名称。
browserPresence false 当已登录的用户使用页面时(过去 10 分钟内有输入),持有此标签页的在线状态租约。该租约使会话的计算机保持唤醒。
presenceAlerts false 此标签页显示自己的通知。通过在线状态租约发送:仅在用户正在使用会发出提醒的标签页时,服务器才会跳过手机通知和 Web Push。变更会立即发送。仅当项目的 notification_center 标志开启时才传入 true;否则服务器会忽略它。需要 browserPresence。
presencePageExit false 页面关闭或进入后退/前进缓存时结束在线状态租约,确保标签页关闭后立即结束的回合仍会发送通知。开启时,pagehide 会报告离线,且每次离线报告都通过 keepalive 发送,因此可在页面结束后继续存在。关闭时:不发送 pagehide 报告,也不使用 keepalive。关闭中的标签页仍会变为隐藏并报告离线,但浏览器可能在页面卸载时取消该请求;此时租约会持续到 90 秒后到期。传入项目的 notification_center 标志。变更立即生效,不会发送新的在线状态报告。需要 browserPresence。
repositoryMode — 仅用于遥测。仓库替换前创建的会话无需此选项,也会启动并使用项目的当前配置运行。只有其 /workspace 克隆仍来自旧仓库,因此从该克隆推送前需要先执行 rebase。

发送采用乐观更新。send 会立即显示你的消息,随后流式 事件填入智能体的回复。

rewind(messageId) 从不创建会话。它使用来自 POST /start 的规范会话。运行时会恢复文件状态,并保留已移除的对话记录 路径,使其可恢复。下一条被接受的提示会提交替代路径。

其他稳定的 hooks

@beeuni/sdk/react 还导出了 React Query hooks,用于不需要 运行中会话的数据。每个 hook 都对应 客户端 上的一个方法,无需 provider。

Hook 读取内容
useProjectModels(projectId) 项目可选的模型。
useProjectModelPickerCatalog(projectId) 每个传输模型的原始 /model-picker 记录(reasoning_options、temperature、limit),以及每条符合条件的托管路由的 managedPricingRoutes 客户价格估算。实际提供服务的路由决定最终的积分扣费。
useProjectSessions(projectId, { parent, startedBy, q, limit }) 单个项目的会话,分页返回。parent: 'root' 返回带有 child_count 的顶层会话;startedBy: 'me' | 'others' | 'automated' 按运行发起者筛选;q 搜索查看者有权打开的所有会话,而不只是已加载的页面。每组筛选条件都有独立的缓存条目。
useSessionChildren(projectId, parentSessionId, { q, limit, enabled }) 单个父会话生成的会话(parent=<id>),按最新优先排序,分页返回。在父会话打开之前设置 enabled: false。
useVisibleAgents({ projectId }) 项目中可见的智能体。
useProjectConfig(projectId) 项目的运行时配置:默认智能体、命令。
useProjectSecrets(projectId) 密钥:列出、添加、移除及个人覆盖设置。
useAccountSecretResources(accountId) 账户的提供商密钥元数据和使用授权。绝不返回密钥值。
useSessionProviderSecretPools(projectId, sessionId) 已配置的池,包括空选择;setPool 更新一个提供商并刷新会话缓存。
useProjectTriggers(projectId) 触发器:列出、创建、更新、移除、触发。
useProjectTriggerEventTypes(projectId, connector) 连接器可据以触发的应用事件。在设置 connector 之前保持空闲。
useChangeRequests(projectId, status?) 变更请求:列出、打开、合并、关闭、请求修改。
useRuntimeSupports(capability) 根据运行时重连轮询器读取的 /beeuni/health 响应,判断当前打开会话的运行时是否提供某项功能(session.rewind、session.compact、…)。收到第一个响应前为 true。参见运行时支持的功能。
useNotificationInbox({ userId, limit?, enabled? }) 调用者收件箱的最新一页(limit 默认为 20)、unreadCount,以及 markRead(ids)、markAllRead()、markSessionRead(sessionId)。页面可见时每 60 秒轮询一次,获得焦点时重新获取。写入会立即更改缓存,失败时恢复缓存,随后重新获取所有收件箱条目。没有 userId 就不发送请求。
useNotificationPreferences({ userId, enabled? }) 调用者按类型设置的推送和邮件偏好、email_available,以及 update(patch)(乐观更新)。
useSessionWatch({ userId, projectId, sessionId, enabled? }) 单个会话的 { watching },以及用于将其静音或关注的 setWatching(watching)(乐观更新)。
resetRuntimeQueries(queryClient) 不是 Hook。清除所有缓存的运行时查询(会话、消息、智能体、命令、差异)。在重启或重新加载配置导致会话运行时被替换后调用,使下一次读取来自新的运行时。

这三个通知 Hook 以 userId(qk.notifications)作为缓存键, 因此在同一标签页中切换用户时,绝不会显示上一个用户的收件箱。请传入 已登录用户的 ID。

通知由每个项目的 notification_center 功能标志控制,默认关闭。使用 enabled: useFeatureFlag(projectId, 'notification_center').enabled 控制每个 Hook,确保 关闭该标志的项目不发送任何通知请求。

连接器设置链接

智能体可以生成一键连接链接(/connect/<token>)。该流程的数据 和运行时策略位于 SDK 中;宿主基于它们渲染自己的对话框,并 注入自己的适配器。

导出项 作用
useConnectorSetup(token, options) 完整流程:加载链接指定的内容、POST /start,通过注入的 openPopup 打开提供商的托管页面,按 3 秒 / 5 秒 / 5 分钟的调度轮询 POST /finalize,并报告 phase(loading、ready、starting、opened、connected、error)、应用信息、错误以及账户被授权使用的身份。options 携带宿主的 backendUrl、storage 适配器和 openPopup;onOpened 在每次弹出窗口打开后触发。
useConnectorLinkInfo(token, options) 每个链接令牌都有一个基于存储的共享缓存,因此所有卡片和模态框共用一次对限流公共路由的 GET 请求,并在首帧绘制应用标志。使用相同的 backendUrl/storage 选项。
createConnectorLinkInfoCache(fetchInfo, { storage }) 缓存工厂本身(不依赖框架,存储由外部注入),供需要自有实例的宿主使用。
resolveConnectorStart({ start, finalize }) /start 的结果规则:托管 URL → 弹出窗口,已连接 → 完成一次,否则显示错误消息。
nextConnectorPollDelay(attempt, elapsedMs) 有时限的轮询调度(首次 3 秒,之后 5 秒,5 分钟后停止)。

连接器数据

useConnectorQuery(projectId, slug, action, args, { account, enabled, staleTime }) 以缓存查询的形式读取单个连接器操作的输出。error 是一个 ConnectorCallError:根据 error.connectUrl 渲染连接按钮,或根据 error.availableAccounts 渲染账户选择器。仅将它用于读取 操作;每次获取都会运行该操作并写入审计事件。输出在 30 秒内保持新鲜,获得焦点时不会重新获取,且仅在上游出现 429 或 503 时 重试。参见通过代码使用连接器。

提前开始打开会话的读取

prefetchSessionOpen(queryClient, projectId, sessionId) 在会话视图挂载前开始 会话打开读取。它发出一次 GET .../snapshot 请求 (会话行、轮次、队列及前 40 条对话记录消息),并用结果初始化 会话行缓存。随后 useSession 使用该请求,而不是 自行发起请求。

当路由指定某个会话,或会话链接被悬停、获得焦点或被触摸时, 调用它。它是只读的:绝不调用 /start,也绝不唤醒计算机。它 绝不会拒绝。30 秒内对同一会话重复调用不会发送 请求。

import { prefetchSessionOpen } from '@beeuni/sdk/react';

<a
  href={`/projects/${projectId}/sessions/${sessionId}`}
  onFocus={() => void prefetchSessionOpen(queryClient, projectId, sessionId)}
/>;

重新加载时从缓存渲染

两个存储保留用户已经看过的内容,使重新加载或应用冷启动时能在 首帧渲染这些内容,并就地刷新。两者均按用户隔离且有容量限制, 宿主在退出登录时清除两者。

  • createPersistedQueryCache 保留用户导航时使用的 React Query 结果: 账户、项目、项目详情、分页会话列表和会话行 (isPersistableQueryKey)。恢复的条目保留原有的数据年龄,因此会在 挂载时重新获取。默认限制为 1,000,000 个字符和 7 天。
  • createSavedCopyStore 保留服务器发送的对话记录的最近一次保存副本。 使用 setSavedCopyStore 注册它,useSession 会在首帧前绘制 保留的副本。随后,服务器副本按消息 ID 合并到其中, SDK 会在每个轮次结束 3 秒后重新读取它。默认限制为 12 个 会话、1,500,000 个字符和 14 天。超过 300,000 个字符的副本会保留 容量范围内最新的消息;当用户向上滚动时,较早的消息从服务器 加载。
import {
  browserKeyValueStorage,
  createPersistedQueryCache,
  createSavedCopyStore,
  isPersistableQueryKey,
  setSavedCopyStore,
} from '@beeuni/sdk';

const storage = browserKeyValueStorage(); // null where there is no localStorage
if (storage) {
  const cache = createPersistedQueryCache({ storage, userId, shouldPersist: isPersistableQueryKey });
  cache.restore(queryClient); // synchronous with localStorage: no pending frame
  const stop = cache.persist(queryClient);
  setSavedCopyStore(createSavedCopyStore({ storage, userId }));
}

在 React Native 上,将 AsyncStorage 作为 storage 传入。此时 restore 返回一个 Promise。

保存副本的存储仅保留服务器快照:即 API 在轮次结束时写入的对话记录镜像。实时对话记录绝不写入设备,因此 已停止的轮次不会恢复成运行中的轮次。如果运行时读取未返回任何消息, 屏幕上仍保留已保存的消息:这表示计算机丢失了状态,而不是 对话为空。首次返回消息的运行时读取会将它们合并。

退出登录和切换账户

SDK 在内存中按用户保留会话状态:对话记录、待处理的问题 和权限、轮次回执以及模型选择。每次身份变化时都调用 resetIdentityState(): 包括退出登录,以及不同用户登录。也包括 在另一个标签页登录导致用户改变、但页面未重新加载的情况。

在你的登录流程中,用户登录或退出时调用此处理函数。

import { resetIdentityState } from '@beeuni/sdk/react';

let previousUserId: string | null = null;

function onIdentityChange(userId: string | null) {
  if (userId !== previousUserId) {
    queryClient.clear();
    resetIdentityState();
  }
  previousUserId = userId;
}

resetIdentityState() 还会移除模型存储中的 localStorage 条目。它 不会清除宿主自身的缓存(例如 React Query),也不会清除传给 createBeeuni 的令牌来源。请在同一个处理函数中重置这些内容。它绝不会抛出异常。

使用重新加载时从缓存渲染中的存储时, 请在同一个处理函数中清除它们:先执行 await cache.clear(),再执行 await savedCopies.clear() 和 setSavedCopyStore(null)。这两个存储都不会 恢复其他用户的数据,但已退出登录的设备不应保留这些数据。

下一步

  • 会话 — useSession 封装的会话句柄,以及 BeeuniSendError 类型。
  • 参考 — 这些钩子读取的完整 REST 接口。

启动时的已保存历史记录

useSession(projectId, sessionId) 会在其计算机启动时显示已保存的聊天历史记录。 SDK 读取数据库不依赖运行时是否就绪。随后,实时消息会按 原始 ID 进行协调。宿主无需单独请求历史记录,也无需单独的消息存储。

已保存历史记录的渲染方式与实时对话记录完全相同:文本、推理,以及每次工具调用 及其输入、输出和错误。子智能体的完整视图读取该子智能体自身保存的 对话记录:将父级作用域和 savedChild: true 传给 useSessionSync(subAgentSessionId, { beeuniSessionScope, savedChild: true })。初始窗口包含最新的 40 条消息, 最多 1,000,000 个字符。向上滚动会从已保存历史记录中加载更早的窗口,即使 计算机已关闭也可以。需要计算机的附件会在 运行时就绪后可用。

savedTranscript 告知宿主在计算机启动时应渲染什么。在 已保存副本仍可能到达时,它为 loading;消息进入 messages 后,它为 shown;在运行时响应前 无法显示任何内容时,它为 none。未知状态是 loading,绝不是 none。

没有任何需要等待的内容时,conversationEmpty 为 true。已保存副本证明 对话为空:对该会话运行时的完整读取没有找到任何消息。此后没有轮次结束, 也没有任何未结束或排队中的内容。缺失记录绝不会使它为 true,因此有历史记录的旧会话 绝不会被显示为空。会话在启用历史记录保存后的首次启动时获得这一证明。

if (s.messages.length === 0) {
  // Nothing saved and nothing to wait for: the user can type now.
  if (s.conversationEmpty) return <Composer />;
  // One round trip for a saved copy; the computer can take minutes.
  return s.savedTranscript === 'none' ? <Booting stage={s.stage} /> : <MessageSkeleton />;
}

2026-09-28 之前保存的工具调用丢失了输入和输出。服务器返回每个调用时, 会提供其保留的标题或元数据能够证明的输入:命令调用的命令和输出、文件调用的路径、 搜索的模式、子智能体的描述、获取请求的 URL、待办列表、技能的名称。 会话下次启动时会再次逐一保存全部历史记录。

待处理提示词

useSessionPrompts(projectId, sessionId) 将提示词提交到持久化会话收件箱。 服务器会自动运行待处理提示词,每个提示词各占一个轮次。

传入 placement: 'transcript' 可在对话中显示待处理消息。 它会先于每个 composer 条目运行,并在 当前工具调用完成后结束正在进行的响应。 传入 placement: 'composer' 可将其保留在输入框上方的可编辑列表中。 它会等待正在进行的响应结束。每种放置位置都保留提交顺序。 传入 delivery: 'steer' | 'queue' | 'interrupt' 可指定提示词如何送达 正在运行的轮次。steer 会在轮次的下一步将其交给该轮次;该轮次不会 停止。queue 会等待轮次结束。interrupt 会在 正在运行的工具结束后结束轮次。仅传入 delivery 时,SDK 会发送它隐含的放置位置: interrupt 对应 transcript,其他值对应 composer。无法 引导轮次的引导行显示为 delivery: 'queue',其 steer_fallback 设为 unsupported、 not_prompter 或 turn_ended。interrupt(promptId)(“Stop and send”)会将 等待中的行变成对话记录条目;已经发送出去的行会返回 409。

网页输入框在轮次运行时将 Enter 作为 steer 发送,在会话空闲时则作为 对话记录发送。Command+Enter(Windows/Linux 上为 Control+Enter) 使用输入框放置位置。Shift+Enter 插入换行。

列表行包含 full_text,用于在重新加载后提供完整预览。text 仍然 为旧客户端提供有长度限制的预览。列表响应不包含附件 字节。删除操作会返回完整提示词,以便无损撤销。

待投递状态与正在进行的响应

提示词在持久化收件箱中等待时,useSessionWorking() 会保持 state: 'working'。 这使 Stop 按钮保持可用。pendingDelivery: true 标记仍在 送往运行时途中的提示词;只要 state 为 working,网页应用就会显示一个工作中指示器,无论该标志的值是什么。运行时中的活动轮次和新的运行时 活动会清除此状态。取消操作超时并不能确认执行已停止。

工作进程认领只检查准入条件,并让提示词继续等待。投递在 准入成功后开始。已确认的活动轮次会清除待处理状态的显示, 即使上一次收件箱快照仍列出该提示词。在此交接过程中,运行时活动 会保留活动轮次的消息 ID。

网页输入框将 Command/Ctrl+Enter 命名为 Queue List,将行上的“Stop and send”命名为 Quick Queue。Quick Queue 条目优先运行。等待中的 Quick Queue 条目显示为已 投递轮次之后的着色气泡,没有状态文字。Queue List 条目在投递开始前仍可编辑。 服务器会根据确切的运行时证据恢复遗漏的完成事件,包括 重新加载之后,且不会将无法访问的运行时视为已完成的轮次。

每次单独按下 Enter 都会立即显示对应的待处理提示词,即使之前的 接收请求尚未完成。只有投递失败才会添加文字:失败原因、 Retry 和 Remove。执行顺序仍由服务器收件箱管理。

钩子从会话的控制流 (GET .../sessions/:id/events?channels=control)读取队列变化,每个会话只有一个连接, 无论有多少组件挂载它。收件箱行的每次写入都会作为 beeuni.control.queue 帧到达。GET .../prompts 轮询仅在该 流未连接时运行。

轮次为何结束

被中断轮次的对话记录仅包含 MessageAbortedError。 控制平面会保留其已知的各轮次结束情况:沙箱 给出的原因,例如 SandboxMemoryGuard,以及哪些轮次终止时没有 给出原因。人为请求的停止——Stop 按钮、abort()、向忙碌会话 发送的提示词——会记录为请求,绝不会报告为 失败。

useSessionTurnOutcome(projectId, sessionId) 从 useSessionWorking() 持续更新的缓存中返回 last_ended、recent_failures 和读取时间 atMs。 它不会发起请求。将结果连同 对话记录中该轮次的信息一起传给 turnEndNotice:

const outcome = useSessionTurnOutcome(projectId, sessionId);
const notice = turnEndNotice(outcome, turn.userMessage.info.id, {
  hasError: Boolean(turnError),
  isAbort: turnErrorIsAbort,
});
// notice: { kind: 'sandbox-memory', usedPct: 97, detail: 'sandbox memory at 97% …' }
//       | { kind: 'cause', name: 'SomeGuard', message: '…' }
//       | { kind: 'unexplained' }
//       | null

宿主将每个 kind 映射为自己的文案,绝不解析沙箱的 消息。null 表示“显示对话记录所显示的内容”:真实错误保留, 中止则不渲染任何内容。只有在读取时间距轮次结束已过 几秒后,才会报告原因不明的失败,因为原因往往在中止后的下一帧 才到达。turnEndCause(outcome, messageId) 返回记录的原始 原因,或 null。

这个页面有帮助吗?