@cubos/agent-sdk

中文文档
0.0.1174337发布于 2026/11/07SEE LICENSE IN LICENSETypeScriptES Module零依赖近 30 天下载 1.2万

Cubos代理谈话的客户端 API. 到处都有跑步.

中文使用文档
文档目录16 节
  1. 代号: 由您的后端铸造, 绝不在客户端中
  2. 对话
  3. 告诉探员屏幕上的内容
  4. 日志本身,当谈话不够时
  5. 重新打开是免费的
  6. 倾斜
  7. 反应部分
  8. 现场对话列表
  9. 图像
  10. 语音信息
  11. 客户工具
  12. 对话表面
  13. 管理员
  14. 错误
  15. 费率限额
  16. 还在包裹里

@cubos/代理服务器

库博斯特工的客户端 两个表面: 对话,用于您的最终用户应用程序 管理员为了你的后端.

零从属. 仅使用 fetch, (中文(简体) ), ReadableStream 财务报告和审定财务报表 AbortController,所以同样的代码运行在浏览器,节点 \ 18, Bun, Deno和Cloudflare工人.

bun add @cubos/agent-sdk    # or npm / pnpm / yarn

代号: 由您的后端铸造, 绝不在客户端中

一个Cubos Agent api key是全租户,而且 绝对不能到达浏览器您的后端将它切换成一个短命的符号, 范围可限制给一个用户 :

import { createAdminClient } from "@cubos/agent-sdk";

// On YOUR server.
const admin = createAdminClient({ baseUrl, apiKey: process.env.CUBOS_API_KEY! });

const { token, expires_at } = await admin.tenant("acme").userTokens.create({
  user_id: session.userId, // from YOUR auth, never from the client
  agent_slugs: ["support"],
  ttl_seconds: 900, // optional; defaults to 3600
});

密钥需要 : user_tokens:write 赠款。 由此而来的信使只到达了对话的终点,只有该用户自己的对话——无法从客户端扩大它.

agent_slugs 列出该符号可能打开对话的代理. ttl_seconds 默认为3600,上限为86400. 没有撤销列表:更喜欢最短的TTL你的应用可以舒适地刷新. 封锁用户( E)users.block)在下次请求时,其令牌无效.

对话

import { createUserClient } from "@cubos/agent-sdk";

const client = createUserClient({
  baseUrl: "https://agent.acme.com",
  // Called per request — cache it yourself. On forceRefresh, return a freshly
  // minted token (the SDK only asks for that after a 401).
  getToken: async ({ forceRefresh }) => fetchTokenFromMyBackend({ forceRefresh }),
});

const conversation = await client.createConversation({ agentSlug: "support" });
await client.sendMessage(conversation.id, "My invoice was charged twice");

const subscription = client.subscribe(conversation.id, {
  onMessage: (m) => console.log(m.role, m.content),
  onActivity: (a) => setTyping(a.isProcessing || a.hasPendingTurn),
  onTodos: (todos) => setPlan(todos),
});

subscription.close();

如果你已经持有一个符号——一个脚本,一个测试——直接通过作为 token: "…" 而不是写一个结束。 静态符号不能被更新, 因此它最终会永远过期; 使用 getToken 对于任何真实的东西.

subscribe 回填连接上的历史,然后从任何下降后的确切点恢复,所以它提供的是整个对话——不需要将其与 listMessages,用于向后呼回漫长的历史.

告诉探员屏幕上的内容

await client.setContext(conversationId, `screen: customers\nfilters: status=active`);

只要屏幕动了就叫它——它不写事件,也不开始转弯,所以它 在每次航行,甚至是中转时,都是安全的。 服务器向代理显示 下次一转看对话, 只有当它改变,所以 重复一个不变的上下文并不花费什么.

这是原始路线. @cubos/agent-sdk-react是因为 useConversation({ context }) - 浏览11页 输入任何内容之前的屏幕会发出一个请求,而不是十一个.

应用程序所能达到的替代方案是将上下文前缀在用户的上 留言。 将它花在每一个消息, 把它放在记录,除非每一个 使路径剥出它, 并且只为谁拥有作曲家.

onActivity 在同一条溪流上,订单是您可以基于的保证:一个框架说结束的转弯在结束它的答复之前从未交付. 关闭上面的打字指示器, 答案已经掌握在手了 。 (它过去来自对话-元数据流,第二个连接,没有针对日志的订单,它们之间的间隙——一毫秒在本地插座上,几秒通过一个代理——显示一个完全的转折,里面什么都没有. )

对于一个错误交易的长篇对话:每一次重联重播整个日志. 通过一个光标, 溪流从那里开始, 留给历史 :

const page = await client.listMessagesPage(id, { limit: 50 });
setMessages(page.messages);

// Only what is newer than the page. Nothing is lost in between: the server
// catches up everything past the cursor before going live.
client.subscribe(id, { onMessage: append }, { since: page.latestChangeSeq ?? undefined });

// Scrolled to the top — the page before this one.
if (page.hasOlder) {
  const older = await client.listMessagesPage(id, { before: page.oldestSeq, limit: 50 });
  prepend(older.messages);
}

@cubos/agent-sdk-react 电线完全接通 loadOlder 编号 : hasOlder。 。 。 .

日志本身,当谈话不够时

对话表面故意隐藏事件日志: 一个聊天抽取消息, 没有 llm_call 作行相会. 但隐藏并不等于隐瞒 订阅同时携带,需要日志的应用程序从同一处获得 地点 :

const page = await client.listEventsPage(id, { limit: 50 });

client.subscribe(
  id,
  {
    onEvent: (e) => append(e), // every row, in stream order
    onMessage: (m) => draw(m), // …and what it became, if you want both
  },
  { since: page.latestChangeSeq ?? undefined },
);

一个连接,一个光标,一个赶上. onEvent 预测前的火灾 看相框,所以一个应用 摄取两者都不必调和.

不同的是承诺,而不是进入. Message | Todo | Activity 实值 - 保管并留在内部; ConversationEvent 这是 服务器自有的 DTO , 当服务器运行时移动—— 相同的交易 REST API 已经提供,因为这些行是 它返回。 帮助它建立一个 操作控制台、 审计视图或活动树; 停留在 Message 用于建设 一个聊天.

@cubos/agent-sdk-react 把它接进 useConversationEvents。 。 。 .

重新打开是免费的

已经上载的谈话被保存在一个有界限的记忆中,所以打开一个 第二次没有要求的油漆:

const start = await client.loadHistory(id); // start.fromCache on a repeat
client.subscribe(
  id,
  { onMessage: append, onCursor: (seq) => (cursor = seq) },
  { since: start.latestChangeSeq ?? undefined },
);

// Hand the current state back whenever it moves; the next open resumes from here.
await client.saveHistory(id, { messages, oldestSeq, latestChangeSeq: cursor, hasOlder });

击打不是呆子. 服务器的事件日志从未重写一行 content, (中文(简体) ), seq 或者说 type,以及每个确实发生的突变——一个暂定事件是: 合并或丢弃的交货收据——通过触发器 发行新版本 change_seq。因此订阅缓存光标重放 在应用程序离开时 究竟有什么变化 插入和更新都一样.

过 cache: null 关掉它, cacheMessageLimit 以改变多少 每次谈话都会保留(默认情况下300个消息;旧信件从 缓存,不是服务器,然后通过呼号返回。 为了活过重载, 执行 ConversationCache 超过索引数据库、 AsyncStorage 或 SQLite :

interface ConversationCache {
  read(key: string): Promise<CachedConversation | null>;
  write(key: string, entry: CachedConversation): Promise<void>;
  clear(key?: string): Promise<void>;
}

钥匙已经送到租户和用户的手中——一个持续储存的商店由 设备上的每个会话,两个用户不能互相读取对方的信件 离开它。 视之为不透明. 一个拒绝的商店被当成一个小姐,所以 腐败的缓存会降解为网络,而不是打破聊天.

租户通过象征性方式解决 GET /me 在第一个呼叫。 过 tenant 在跳过往返的选项中.

倾斜

当您想要所有的东西并且不愿管理光标时 :

for await (const conversation of client.iterateConversations()) { … }

// The 20 most recent messages, newest first.
const recent = [];
for await (const message of client.iterateMessages(id)) {
  recent.push(message);
  if (recent.length === 20) break;
}

突出不会获取下一页 .

pageSize 计数 事件,而不是消息:日志还持有代理工具的呼叫和簿记,因此一个页面可以持有比您要求的更少的信息. 执行者对此负责;一页没有消息,不会结束行走.

反应部分

您的应用程序可以让代理回答  组成部分——价格图,a 取出取枣者,取出定单牌来取取取——而不是单独打分. 代理人可能使用的东西 来自 组件库a. 管理API的操作作者; 您的应用程序允许打开屏幕可以绘制的 :

const conversation = await client.createConversation({
  agentSlug: "support",
  componentLibraries: ["support-widgets"],
});

setComponentLibraries(id, slugs) 随时替换列表 — 发送 完成 ; 空列表将代理人带回平面标记下。 变化 从代理的下一个回合.

分出即是信相界. 构成部分摘要、说明和 向模型显示示例 逐字记录,所以写作是快速的 内容和需要一个 api key; 启用库只是线程, 这就是为什么 这个客户端 持有一个短命的最终用户标志 能够做到这一点, 仅此而已.

授权呼叫及其 list 对应回复 tags,每个 包含启用的库决心,按照代理查看的顺序 已索引。 对照您可以实际渲染的内容检查: 没有渲染器的标记是 一个阻隔 代理将乐于写 和没有画.

每条代理信息都会被分割成 blocks,已解析道具 并对照图书馆的计划:

{
  message.blocks?.map((block, i) =>
    block.type === "markdown" ? (
      <Markdown key={i}>{block.text}</Markdown>
    ) : (
      <MyComponent key={i} tag={block.tag} {...block.props} />
    ),
  );
}

一个组成部分是: 总是自我封闭 没有 <Tag>…</Tag>. 无论如何 应该用道具来显示 周围的标记会留在外面 自有块 :

// leia **isto**
// <PriceChart symbol="PETR4" points={[1,2]} />
[
  { "type": "markdown", "text": "leia **isto**" },
  { "type": "component", "tag": "PriceChart", "props": { "symbol": "PETR4", "points": [1, 2] } }
]

值得我们知道:

  • blocks 总是在代理信息. 无组件的答复是 一个 markdown 块,所以你写一个渲染路径,而不是两个.
  • 服务器执行合同. 没有启用的图书馆服务组件, HTML,或者在信件被写入之前,错过了"计划"的道具被拒绝—— 代理人得到错误并重写。 验证是完整的JSON Schema,不是 浅色类型检查,拒绝时指定失败的路径 (单位:千美元)points.0.v: "muito" is not of type "number"),所以代理可以修复.
  • content 仍然是整个消息 如探员所写,如果你愿意 不如自己来.
  • 块是读取时产生的,从不存储. 丢弃库不会 重写旧消息,所以保留一个渲染器 任何记录可能仍然 包含.
  • 每次对话都启用库, 只有当您的应用程序是渲染器时才启用 . 在频道支持的对话(WhatsApp, Telegram)上进行 外部频道则在频道上声明其库.
  • 交互是您的: 代理输出块, 您的组件处理 点击,不管它意味着什么 sendMessage。 。 。 .

现场对话列表

const page = await client.listConversations();
client.subscribeToConversations({ onConversation: (c) => upsertAndResort(c) });

每当对话活动取得进展时,就再次发出,这就是保持 一张没有投票的聊天列表.

图像

await client.sendImage(id, file, { caption: "is this the right charge?" });

// Up to 10 in one message, so the agent reasons over the set rather than one
// turn per picture. A label names an image for the model, which lets it answer
// about "the receipt" instead of "the second image".
await client.sendImages(
  id,
  [
    { image: receipt, label: "receipt" },
    { image: statement, label: "statement" },
  ],
  { caption: "compare these" },
);

png,jpeg,webp和gif,每个可达10MB. 特工在它的模型有视觉时就从原生地看待画面,否则就是从特工倒置的视觉模型中描述出来——无论是哪种方式,都花费一转并作为一个预算.

一个图像像一个 Message 谁 attachments 说明媒体及其 content 是标题. 没有标题的发送是正常的,所以渲染 attachments 即使当 content 为空. 字节又回来了 Blob,而不是URL,因为信使在信笺中行走——a <img src> 指向航道将到达无证:

const blob = await client.fetchAttachment(conversationId, message.id, attachment.id);
img.src = URL.createObjectURL(blob); // revoke it when the element goes away

当反向视觉模型描述一幅图片时,该描述不包含在 是模特在自言自语 屏幕,而不是用户发送的信息.

语音信息

await client.sendAudio(conversation.id, blobFromMediaRecorder);

片段由代理商的STT模型在转弯运行前进行转录. 它像一个 Message 携带一个 audio 附件,与 content 持有抄本——直到STT完成为空,这是客户端显示玩家尚未下方文本的窗口. 回放的字节来自 fetchAttachment 我也一样.

客户工具

没有服务器侧执行的工具:代理机呼叫一个, 轮到它了 暂停,你的应用程序答案.

const session = await client.serveClientTools(id, {
  tools: {
    get_location: {
      description: "Where the user is, as their device reports it.",
      inputSchema: { type: "object", properties: {} },
      readOnlyHint: true,
      handler: async () => ({ city: "Fortaleza" }),
    },
  },
});

宣布节目,观看谈话 打电话,租赁每一个这样两个 选项卡不运行两次,在慢处理者工作时延长租约,并重试 效果 POST — 一旦副作用出现, 答案就值得多次尝试 已经发生了.

它打开自己的活动流来进行监视. 如果您已经订阅 谈话,不要支付第二个连接:

const session = await client.serveClientTools(id, { tools, watch: false });
client.subscribe(id, {
  onOpen: () => session.poke(), // catches up after a (re)connect
  onClientToolCall: () => session.poke(), // and on every new call
  onMessage: append,
});

setClientTools 宣告时不运行任何内容——在 第一个信息,因为一个工具 从来没有告诉过代理 无法调用在 转过来. @cubos/agent-sdk-react 做这一切为你.

对话表面

me() 编号 : refreshIdentity()信使的用户、租户和代理人
listConversations() 编号 : iterateConversations()带有光标的一页或全部
createConversation()打开与代理人的对话
getConversation() 编号 : renameConversation() 编号 : archiveConversation()
listMessages() 编号 : iterateMessages()历史
listMessagesPage()历史加光标以呼回和指针 subscribe({ since })
loadHistory() 编号 : saveHistory() 编号 : forgetHistory()对话缓存
sendMessage() 编号 : sendAudio()用户消息,输入或已发
sendImage() 编号 : sendImages()一个图像,或者一个信息中最多10个
setClientTools() 编号 : serveClientTools()声明您的功能,并运行调用
setComponentLibraries() 编号 : listComponentLibraries()代理服务器可能使用的组件库
fetchAttachment()作为附件的字节 Blob
subscribe() 编号 : subscribeToConversations()状态

他们都接受 AbortSignal。 。 。 .

暴露类型Conversation, (中文(简体) ), Message, (中文(简体) ), Attachment, (中文(简体) ), Block, (中文(简体) ), EnabledComponents, (中文(简体) ), Todo, (中文(简体) ), Activity)是一个被管理 表面, 窄于服务器的 DTO : 事件日志有数十种类型 给操作员仪表板 将这里的仪表板钉上 内部重塑一个突破性的变化.

管理员

以 api key 认证的全 API, 按资源和范围命名 每个租户:

import { createAdminClient } from "@cubos/agent-sdk";

const admin = createAdminClient({ baseUrl: "https://agent.acme.com", apiKey });

await admin.tenant("acme").agents.list();
await admin.tenant("acme").users.block(userId);
await admin.tenants.list();
await admin.apiKeys.rotate(keyId);

类型来自服务器的 OpenAPI 文档(Schemas["Agent"],(......), 在每个路由更改中重新生成——所以客户端自行跟踪服务器. admin.raw 是尚未被包裹的路径的逃生舱口.

仅限服务器侧. 一个 api key 是全租户的: 它能读写 每一个用户在租户的对话。 使用 createUserClient 在浏览器中.

错误

SDK抛出的一切 AgentError,所以一个 catch 涵盖两个 重要案例:

try {
  await admin.tenant("acme").agents.get("support");
} catch (err) {
  if (err instanceof AgentNetworkError) {
    // Never reached the server: wrong URL, server down, CORS, or the timeout
    // (`err.timedOut`). `err.cause` holds the original failure.
  } else if (err instanceof AgentApiError) {
    // The server answered, and refused.
    if (err.isNotFound) …
    if (err.isConflict) …          // 409
    if (err.isAuthError) …         // 401/403
    if (err.isRetryable) …         // 429 or 5xx
  }
}

AgentApiError 携带 status, (中文(简体) ), requestId 和逐字记录 body (单位:千美元)err.json() 来分析它. AgentConfigError 由客户端构建而来 而不是打个电话——通常是 baseUrl 没有 http://。 。 。 .

每个请求都有30s的超时;用 timeoutMs,或 0 以禁用. 溪流是豁免的,因为保持开放是重点.

费率限额

服务器预算每个实例的最终用户令牌:大约20个代理转出,300个 其他电话每分钟, 加上一个同时流的上限。 拒绝是429 Retry-After,以及 sDK处理它 它等待广告的间隔 默认情况下重放请求两次. 429表示拒绝,而不是半申请,所以 重放一个 POST 这是安全的.

它放弃了,当复试用完,没有 Retry-After 或等待超过20秒时——阻断呼叫者 几分钟比现在告诉他们还糟. maxRetries: 0 选择退出。 中止期间 等待会取消重试而不是完成它.

api keys 豁免这一切.

瞬间流失败不结束订阅: SDK 重新连接 指数反转并报告通过 onError,用于伐木或a "重联"提示.

三个看起来像是沉默的失败得到处理而不是报告。 A 类 401 (中文(简体) ) 购买一个立即重新连接的硬币刷新——短寿命 在流出超过预期的流水中过期的,不是 错误的配置——只有第二次拒绝才是最终的. A 类 429个 ——每个用户 在打开的溪流上盖, 一个标签太多到达了—— 等待服务器的 Retry-After 并重新连接, 因为盖子清除了任何标签关闭的时刻。 A 类 停止发送的连接 任何东西中保留保留备注 放弃并重新开放 idleTimeoutMs (30s,是服务器的两倍) 间隔 : 否则半开插座让读者永远等待 没有反应错误 .

一条溪流必须 在它被信任之前证明自己。服务器发送一个 每个连接上都有一个框架,所以直到一个连接发出一个——并且再次 只要一结束.. subscribe 投票记录相同 每3次通过 after 光标, 和对话排行。 在一个健康的网络上,框架土地 第一次民意测验的2分之多 从未有过民意测验 在一个网络上 代理持有一个 text/event-stream 回复直到结束( 公司 TLS) 检查,一些抗病毒药物——克罗姆与说话的宿主谈判HTTP/1.1 h2是事实,每一个连接 静静地静静,直到有东西切断它 答复以前只在重新装入一页后才出现;现在它在3s之内出现. 这个 相同的规则涵盖了一个下注和后面的再连接之间的间隔. 一个 光标为两条路径服务, 因此没有发送两次 .

最终结果是什么? 4xx幸存下来, 被删除的对话.. 关闭对善和火灾的订阅 onFatal (单位:千美元)onError 见之也. 这是一个UI必须浮出水面: 一切都会变成,没有东西会 永远到来.

还在包裹里

  • @cubos/agent-sdk/sse ——Server-Sent Eventions的阅读器本身(重接,光标重放,倒放),为消费者直接与API交谈,只想要那块.
  • @cubos/agent-sdk-react ——与回放钩相同的对话表面,在单独的包中. 胡克:它使无所事事,所以聊天UI保留你的.
  • @cubos/agent-sdk-react-dom ——少数通用性足以共享的组件,从代理人的平分开始. 只有Web,它本身也风格.

版本与发布记录

@cubos/agent-sdk 共发布 113 个版本,首次发布于 2026/09/08;过去 12 个月发布了 113 个版本。

版本发布日期距今
0.0.1174337latest2026/11/073 天前
0.0.11725352026/11/055 天前
0.0.11717452026/11/046 天前
0.0.11714852026/11/046 天前
0.0.11712492026/11/047 天前
0.0.11704982026/11/037 天前

关于 @cubos/agent-sdk 的常见问题

@cubos/agent-sdk 是什么?

Cubos代理谈话的客户端 API. 到处都有跑步.

如何安装 @cubos/agent-sdk?

在项目目录运行 npm install @cubos/agent-sdk,或使用 pnpm add / yarn add / bun add。

@cubos/agent-sdk 支持 TypeScript 吗?

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

@cubos/agent-sdk 支持 ESM 还是 CommonJS?

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

@cubos/agent-sdk 需要什么版本的 Node.js?

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

@cubos/agent-sdk 可以商用吗?

@cubos/agent-sdk 使用 SEE LICENSE IN LICENSE 许可。许可条款写在包内的独立文件中,请阅读该文件确认可否商用。以上为概括,不构成法律意见,请以许可原文为准。

@cubos/agent-sdk 的下载量有多少?

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

@cubos/agent-sdk 还在维护吗?

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