前端 SDK
Preview
快速开始
先在应用后端配置 用户 token 接口,再使用 createNuva() 初始化客户端。
import { createNuva, Message } from "@nuva/sdk";
const nuva = createNuva({
getAccessToken: async () => {
const response = await fetch("/api/nuva-token", {
method: "POST",
credentials: "same-origin",
// 按应用的鉴权配置添加 CSRF header。
});
if (!response.ok) {
throw new Error(`Token request failed: ${response.status}`);
}
const data: unknown = await response.json();
if (typeof data !== "object" || data === null ||
!("token" in data) || typeof data.token !== "string" || !data.token) {
throw new Error("Invalid token response");
}
return data.token;
},
});
const main = await nuva.sessions.getMain();
const agent = nuva.agent({ sessionId: main.id });
const receipt = await agent.send(Message.text("开始广告创作,请先列出需要的产品资料。"));| 配置项 | 类型 | 说明 |
|---|---|---|
getAccessToken | () => Promise<string> | 返回当前登录用户的 Nuva 访问 token。获取失败时抛出错误。 |
每个登录用户复用一个客户端实例。退出或切换账号时调用 nuva.close(),然后为新用户创建实例。服务凭证仅配置在后端。
React 接入
在组件树中挂载 NuvaProvider。调用 useAgent({ sessionId }) 读取消息并发送操作。
import { NuvaProvider, useAgent } from "@nuva/sdk/react";
import type { NuvaClient } from "@nuva/sdk";
import { MessageList } from "./MessageList";
function App({ client, sessionId }: { client: NuvaClient; sessionId: string }) {
return (
<NuvaProvider client={client}>
<Conversation sessionId={sessionId} />
</NuvaProvider>
);
}
function Conversation({ sessionId }: { sessionId: string }) {
const agent = useAgent({ sessionId });
if (agent.loadStatus === "loading") return <p>加载中…</p>;
if (agent.loadStatus === "error") return <p role="alert">{agent.error.message}</p>;
return (
<>
{agent.freshness === "stale" && <p>连接中断,正在显示已保存的内容。</p>}
<MessageList messages={agent.messages.items} agent={agent} />
</>
);
}MessageList 是应用内的消息组件。按 message.content 的顺序渲染内容,并使用 agent.respond() 提交客户端工具结果。
| 字段 | 值 / 类型 | 说明 |
|---|---|---|
loadStatus | loading | ready | error | 当前会话数据的加载状态。 |
session | Session | 会话 ID、类型、标题及所属用户;ready 时可用。 |
messages | Page<MessageView> | 已加载的消息及历史分页游标;ready 时可用。 |
agentStatus.state | idle | running | waiting_for_response | Agent 执行状态;等待回应时 callIds 列出相关调用。 |
freshness | live | stale | 数据已同步,或暂时显示上次保存的内容。 |
error | NuvaErrorInfo | 加载失败时存在;ready 状态下也可能包含同步错误。 |
卸载组件只结束本地订阅。已开始的服务端工作继续运行。
消息与引用
发送消息
await agent.send(Message.text("继续修改第二条脚本。"));
await agent.send(
Message.create()
.text("修改 ")
.mention(script)
.text(" 的开场,保留其他内容。")
);
await agent.send(
Message.create().replyTo(existingMessage).text("使用这一版。")
);| 方法 | 参数 | 用途 |
|---|---|---|
Message.text() | string | 创建纯文本消息。 |
Message.create().text() | string | 追加文字。 |
.mention() | RecordLink | 在文字中插入记录引用。 |
.attach() | RecordLink | 添加消息附件。 |
.quote() | RecordLink | MessageView, string | 引用记录或消息中的选定文字。 |
.replyTo() | MessageView | 指定被回复的消息。 |
构建方法返回新的消息值。send() 成功后返回已保存消息的引用;Agent 回复通过消息订阅到达。
{
"requestId": "request_21",
"message": {
"sessionId": "work_product_a",
"messageId": "message_12"
}
}记录引用
将 API 返回的 RecordLink 传入 mention() 或 attach()。revision 指定记录版本。
{
"ref": { "kind": "script", "id": "script_1", "revision": "1" },
"label": "脚本 1"
}接收消息
{
"ref": { "sessionId": "work_product_a", "messageId": "message_10" },
"role": "assistant",
"createdAt": "2026-10-12T18:00:00Z",
"state": "complete",
"attachments": [],
"content": [
{ "id": "part_text", "type": "text", "format": "markdown", "text": "脚本已生成。" },
{
"id": "part_script_1",
"type": "resource",
"record": {
"ref": { "kind": "script", "id": "script_1", "revision": "1" },
"label": "脚本 1"
}
}
]
}| 字段 | 说明 |
|---|---|
ref | 来源会话 sessionId 与消息 messageId。转发消息保留原引用。 |
role | user 或 assistant。用户消息的 metadata.user 提供发送者身份。 |
content | 有序内容列表:text、mention、resource、quote、dynamic-tool 或 data-host-call。 |
state | streaming | complete | interrupted | failed。failed 时包含 error。 |
attachments | 消息附件列表。 |
replyTo | 可选的被回复消息引用。 |
按 SDK 返回的顺序显示 messages.items。流式更新会合并到原消息;无需自行拼接网络片段。
客户端工具
问答、许可确认和内容审核通过消息中的 dynamic-tool 交互。使用 toolName 选择处理函数,使用 toolCallId 关联结果。
{
"type": "dynamic-tool",
"toolName": "nuva.review",
"toolCallId": "call_17",
"state": "input-available",
"input": {
"prompt": "请审核这条脚本。批准后将继续制作提示词。",
"records": [{ "kind": "script", "id": "script_1", "revision": "1" }]
}
}| 状态 | 处理方式 |
|---|---|
input-available | 读取完整 input,显示交互内容并等待用户回应。 |
output-available | 从 output 读取已保存的结果。 |
output-error | 从 errorText 读取失败说明。 |
提交结果
将收到的工具调用和结果传入 respond()。结果必须符合该工具的 outputSchema。
// part 是消息中 state 为 input-available 的工具调用。
const receipt = await agent.respond(part, {
decision: "reject",
text: "先展示产品,再讲困扰。",
});nuva.review 使用 decision: "approve" | "reject" 和 text。无补充说明时传入空字符串。方法在服务端保存结果后返回:
{
"requestId": "request_22",
"call": {
"type": "dynamic-tool",
"toolName": "nuva.review",
"toolCallId": "call_17",
"state": "output-available",
"input": {
"prompt": "请审核这条脚本。批准后将继续制作提示词。",
"records": [{ "kind": "script", "id": "script_1", "revision": "1" }]
},
"output": { "decision": "reject", "text": "先展示产品,再讲困扰。" }
}
}后续 Agent 回复继续通过消息订阅到达。仍在等待回应的调用可从历史消息或转发消息中提交。
提交执行错误
无法取得有效结果时调用 fail()。用户拒绝审核应作为正常结果传给 respond()。
await agent.fail(part, {
code: "EDITOR_UNAVAILABLE",
message: "脚本编辑器加载失败。",
});返回值包含 requestId 和状态为 output-error 的 call。
Host call
通过 onHostCall() 订阅已注册的网站交互。将 toolName 映射到应用的页面、编辑器或其他操作。
const unsubscribe = agent.onHostCall((part) => {
if (part.type === "dynamic-tool") {
if (part.state !== "input-available") return;
host.receive(part.toolName, part.input, part);
} else {
host.receive(part.data.toolName, part.data.input, part);
}
});
// 在组件卸载的清理函数中调用 unsubscribe()。host.receive 是应用提供的处理函数。需要结果的 dynamic-tool 使用 respond() 或 fail() 回传结果。
不要求结果的 host call 使用 data-host-call。按其 input 执行应用操作,无需回传:
{
"type": "data-host-call",
"id": "host_19",
"data": {
"toolName": "workspace.showScripts",
"input": {
"records": [{ "kind": "script", "id": "script_1", "revision": "1" }]
}
}
}Host call 同时保存在消息内容中。历史渲染只展示保存的内容;自动执行时按调用 ID 去重。
在服务端授权配置中启用对应 UI skill。前端处理函数使用该 skill 中注册的方法名和输入格式。
方法参考
| 方法 | 返回类型 | 用途 |
|---|---|---|
nuva.sessions.getMain() | Promise<Session> | 读取当前用户的主会话。 |
nuva.sessions.list(query?) | Promise<Page<Session>> | 分页读取可访问会话。 |
nuva.sessions.get(sessionId) | Promise<Session> | 读取指定会话。 |
nuva.sessions.history(sessionId, query?) | Promise<Page<MessageView>> | 读取已保存的消息历史。 |
nuva.sessions.getMessage(ref) | Promise<MessageView> | 按 MessageRef 读取消息。 |
nuva.agent({ sessionId }) | AgentHandle | 取得该会话的操作句柄。 |
agent.send(message, options?) | Promise<SendReceipt> | 保存用户消息。 |
agent.respond(part, output, options?) | Promise<ResponseReceipt> | 保存客户端工具结果。 |
agent.fail(part, failure, options?) | Promise<HostFailureReceipt> | 保存客户端工具执行错误。 |
agent.subscribe(listener) | Unsubscribe | 立即读取当前状态,并接收后续状态更新。 |
agent.onHostCall(listener) | Unsubscribe | 接收客户端工具及 host call。 |
agent.loadEarlierMessages() | Promise<void> | 加载更早消息并更新 messages。 |
agent.listPendingCalls(query?) | Promise<Page<DynamicToolUIPart>> | 读取 input-available 状态的调用,包括尚未加载的历史。 |
agent.getHostCall(id) | Promise<DynamicToolUIPart | NotificationHostCall> | 按 toolCallId 或 host call ID 读取调用。 |
agent.refresh() | Promise<void> | 重新同步状态;不会重发操作。 |
nuva.close() | void | 关闭本地连接并清理客户端状态。 |
query 接受可选的 cursor 和 limit。返回页包含 items 和 nextCursor;nextCursor 为 null 时已到末页。options 接受可选的 requestId。
错误处理
操作失败时 Promise 抛出 NuvaError。读取 code、message、retryable 和可选的 requestId。
| 错误码 | 处理方式 |
|---|---|
UNAUTHENTICATED | 检查 token 和应用登录状态。 |
FORBIDDEN | 检查用户授权范围。 |
NOT_FOUND | 核对会话、消息或记录 ID。 |
INVALID_INPUT | 按输入 Schema 修正参数。 |
REVISION_CONFLICT | 读取最新记录,核对后重新提交。 |
CALL_OBSOLETE | 读取当前有效的调用,重新确认待提交内容。 |
CALL_ALREADY_RESOLVED | 读取该调用已保存的结果。 |
REQUEST_ID_CONFLICT | 检查 requestId 是否被用于不同的输入。 |
RATE_LIMITED | 降低调用频率,按 retryable 决定是否重试。 |
UNSUPPORTED_CONTENT | 检查消息类型和 SDK 版本。 |
CONNECTION_ERROR / SERVICE_ERROR | 保留用户输入,检查接受状态后恢复或重试。 |
命令错误中的 acceptance 为 not_accepted 或 unknown。unknown 表示尚不能确认服务端是否已保存操作。
重试同一操作时复用相同 requestId 和输入。缺省 requestId 由 SDK 生成;发生状态不明的错误时,保留错误中的 requestId。send() 或 respond() 返回成功仅表示该次输入已保存,后续生成结果从消息订阅读取。