跳转到内容

Node SDK 5 分钟跑通

@tansr/sdk 是 tansr 内核的 headless 语言绑定:prompt 进、事件流出,零 UI 依赖。查询环、工具调度、权限引擎、上下文压缩全部来自内核并已编译内联,你拿到的是与 tansr 命令行同一套运行时,以库的形态嵌进你的应用。

平台应用的 systemPrompt 用于共享角色,SDK 的 system 用于代码管理的业务段;默认 fallback 下显式 system(包括 [])替代平台段。平台选择 prepend 后先保留平台段,再追加 SDK 段,[] 也保留平台段。宿主环境说明放 systemAppend,不触发默认覆盖。

Windows Electron 仍由主进程运行 SDK,renderer 发送的是用户输入。应用提示词及策略功能尚待配套版本发布;支持版本可通过只读 session.applicationPrompt 核对来源与策略,不会返回正文。平台提示词与策略在后续新一轮前刷新,当前执行轮固定;原会话与历史保留,无需重开。完整真值表、平台 PUT / GET 与可运行示例见应用系统提示词

任务运行中需要补充要求时,使用 同轮追加输入getInputTarget / submitInput 与回执查询;无需中断当前执行或新建会话。首期仅 memory 文本,需配套版本发布。

要求
Node.js ≥ 22.19
Electron(如适用) ≥ 39,SDK 运行在主进程
模块制式 ESM only("type": "module";不支持 CJS require()
TypeScript target ≥ ES2022;Electron 项目建议 skipLibCheck: true

发布产物是单文件 ESM bundle 加单文件 .d.ts。第三方运行时依赖只有 undicizodzod-to-json-schema;可选依赖 @vscode/ripgrep 装不上会自动降级,无需处理。

终端窗口
npm install @tansr/sdk

SDK 从哪里拿模型,决定你需要什么凭据:

传什么 适用
令牌档 { token, baseUrl } 终端分发(Electron / 桌面);模型目录与能力位由平台按应用配置下发,本地零配置
托管档(BYOK) model: '别名' + 本地 .tansr/settings.json 自己的服务器 / 脚本,自带模型 API key
注入档 { client, model } 成对 测试(脚本化模型)或自定义接入

三档互斥,混用会在装配期以 invalid_options 拒绝。本文以令牌档为例,因为它是把智能体交给终端用户时的正确形态。

令牌档的凭据流是「三方闭环」:

  1. 你在控制台创建一个桌面应用(或服务端应用),拿到 appidappkey。appkey 只放在你的服务端。
  2. 终端应用用自家登录态请求你的服务端;你的服务端以 appkey 调用 POST /v1/app-tokens,为该用户换一枚短期 app_user 令牌(ttlSeconds 60–86400),只把 { token, expiresAt } 下发终端。
  3. 终端应用把这枚令牌交给 SDK。

官方样板 examples/token-server 演示了换发端点的完整写法(含请求签名)。appkey 永远不出你的服务端——不下发终端、不打日志、不进错误响应、不进版本库。

如果你只是在自己机器上做个脚本,也可以先用托管档:在 .tansr/settings.json 里声明 provider(API key 只写环境变量名,值走环境变量),然后 createSession({ model: 'main' })

import { createSession } from '@tansr/sdk';
// token 由你的服务端换发;baseUrl 是平台网关地址
const session = await createSession({ token, baseUrl: 'https://api.tansr.com' });
session.send('帮我总结这份合同');
for await (const event of session.events) {
if (event.type === 'msg.text.delta') process.stdout.write(event.text);
if (event.type === 'turn.completed') break;
}
session.close();

要点:

  • createSession 是 async,恒 await
  • send() 在空闲时起新一轮;运行中再次 send() 会经内核注入屏障并入当前轮,输入不丢。
  • events 是跨轮连续、seq 单调的 AsyncIterable<KernelEvent>,支持多个消费者同时 for await,各得全量。
  • close() 幂等;不调 close() 的话 for await 不会自然退出——这是最常见的「事件流卡住」原因。

事件是平铺的 discriminated union,直接按 event.type 分支。第一次跑通时建议把类型全部打印出来,感受一轮里发生了什么:

for await (const event of session.events) {
console.log(event.seq, event.type);
}

你会看到大致这样的序列:session.createdturn.started → 若干 msg.block.start / msg.text.delta / msg.block.end →(如有工具调用)tool.proposed / tool.permission.decided / tool.started / tool.completedcost.usage.updatedturn.completed

如果你要做 UI,不要手拼事件:用 createSessionView 把事件流归约成不可变的视图快照,再用 createNarrator 生成人类可读的日志行。两者都随包提供。

现象 原因与处置
invalid_options 三档字段混用,或令牌档传了 config / capabilities(令牌档能力位恒由平台治理)。读消息改代码
assembly_failed 托管档配置不可用、别名解析失败,或令牌档 bundle 拉取失败;cause 保留底层错误
工具全被拒 没接 permission.askUser 且工具非只读——缺省 fail-closed。见会话与事件流
事件流不退出 忘了 session.close()