跳转到内容

嵌入现有应用

@tool-bridge/sdk 把 tool-bridge 作为库嵌入你的应用。你可以注册本地 Tool/Context,把 fetch 接到现有 HTTP 宿主,或通过 connect() 把本地能力反向挂到远程网关。

它适合已有 Node 22+ 应用、需要调用进程内函数或自定义存储的开发者。它不适合希望”安装后自动获得完整托管 gateway”的场景:SDK 不会自动替你提供持久数据库、对象存储、Search、设备网关通道或完整内置 Plugin catalog。

包的根入口以 Node 22 为目标,并直接依赖 node:os、Node ws 和 process.env。HTTP 表面使用标准 Request / Response,但根入口仍是 Node 宿主 API;标准自托管服务使用 Node / PG / S3。

另有一个 runtime-neutral / Hermes-safe 子入口 @tool-bridge/sdk/device,面向 React Native / Hermes 运行时。它是实时设备客户端与显式 Mailbox processor(把本地 tool/context 反向挂到远程网关),不含 createToolBridge,不是完整网关;webSocketFactory 必填,凭证由 credentialProvider 提供(SDK 不存 SK),shell/文件执行器只在 CLI tb connect 里。详见接入本地设备与服务。

  • Node.js 22+;
  • 熟悉 Request / Response Fetch API;
  • 一套生产 StateStore 方案;
  • Admin SK 与可选 SecretStore 加密密钥;
  • 如果对外暴露 HTTP,准备 TLS、域名和宿主生命周期管理。

下面的 Node 示例使用 Hono 的 Node server adapter:

Terminal window
npm install @tool-bridge/sdk @hono/node-server
import { serve } from '@hono/node-server'
import {
createToolBridge,
MemoryObjectStore,
MemoryStateStore,
} from '@tool-bridge/sdk'
const adminSk = process.env.TB_BOOTSTRAP_ADMIN_SK
if (!adminSk) throw new Error('TB_BOOTSTRAP_ADMIN_SK is required')
const tb = createToolBridge({
state: new MemoryStateStore(),
objects: new MemoryObjectStore(),
adminSk,
encryptionKey: process.env.TB_SECRET_ENCRYPTION_KEY,
})
tb.registerTool(
'tools/echo',
{
List: () => [
{
name: 'echo',
description: '原样返回 text',
inputSchema: {
type: 'object',
properties: { text: { type: 'string' } },
required: ['text'],
additionalProperties: false,
},
},
],
Call: (_name, args) => ({ content: { echoed: args.text } }),
},
{ description: '本地 echo 工具' },
)
serve({
fetch: (request) => tb.fetch(request),
port: 8787,
})

registerTool 的 List 是运行时 ~help 与 JSON Schema 的来源;Call 执行实际工具。也可以传入 OperationRegistry,通过 Zod 统一生成 schema 与校验。

在另一个终端先读取节点级工具索引:

Terminal window
curl \
-H "Authorization: Bearer $TB_BOOTSTRAP_ADMIN_SK" \
-H "Accept: application/json" \
http://127.0.0.1:8787/tools/echo/~help

这一层默认是索引,会列出 echo,但不含完整 schema。加查询参数 ?schemas=1 可让节点级一次返回该节点所有工具的 schema;只看单个工具时,直接下钻工具级帮助更省上下文:

Terminal window
curl \
-H "Authorization: Bearer $TB_BOOTSTRAP_ADMIN_SK" \
-H "Accept: application/json" \
http://127.0.0.1:8787/tools/echo/echo/~help

再原样调用命令级路径,body 直接发送 arguments:

Terminal window
curl -X POST \
-H "Authorization: Bearer $TB_BOOTSTRAP_ADMIN_SK" \
-H "Content-Type: application/json" \
-d '{"text":"hello"}' \
http://127.0.0.1:8787/tools/echo/echo
  • 节点级 ~help 默认返回 echo 索引(加 ?schemas=1 可一次取回整节点 schema),工具级 ~help 返回含 outputSchema 的完整契约;
  • 合法调用返回 { "echoed": "hello" } 对应内容;
  • 缺少 text 或加入未知字段时按 schema 被拒绝;
  • 未携带有效 SK 时无法访问受保护路径。

完成示例后应签发受限 SK,并用它重复 help/call。不要让业务调用长期使用 bootstrap Admin SK。

createToolBridge 不读取环境变量来补齐 adminSk 或 encryptionKey。示例中的 process.env 是你的应用显式取值,再传给 SDK;它与标准服务的受保护安装不是同一入口。

配置 责任
state 必填,树、SK、manifest 的持久权威
objects 必填,字节服务;示例 MemoryObjectStore 仅适合易失开发
adminSk 首次 bootstrap 显式身份
encryptionKey / secrets 显式加密根或 SecretStore;缺配置时相关能力 fail closed
storeRepository / storeBackends Store 领域事务与后端解析
mailboxRepository Mailbox 领域事务,另需加密根
remoteAllowlist / maxHops Federation 范围与跳数
pluginBindings / pluginCatalog 同源的进程内代码与 descriptor
providerOAuthFetch 宿主显式提供的受控 OAuth 出站,不回退全局 fetch

单独提供 objects 不等于 Store / Mailbox 已完整可用;生产不能用普通 KV CAS 或内存锁替代领域事务。需要开箱即用的持久化、安装、配置、后端与维护管理时,使用标准 Node 服务。

如果只给 pluginBindings 不给 catalog,运行时代码存在但无法按 export 解析;只给 catalog 不给 binding,则能选择却不能调用。标准宿主从 @tool-bridge/plugins 同源装配两者,自定义宿主也应保持这个不变量。

当前公开 ToolBridgeConfig 没有 SearchIndex 注入字段,因此 SDK 实例不提供 ~search。需要 Search 时使用标准 Node server,并以根 ~describe 验收;不要向 SDK 传入一个类型未声明、运行时也不会装配的 search 字段。

使用 registerContext(path, provider, meta) 注册本地 Context。Provider 至少实现读取所需动词;是否有 Put、Patch、Delete、Search 或 Subscribe 取决于你提供的方法。

客户端应从 Context 路径的 ~help 读取实际命令和 schema,从同一路径的 ~describe 读取可选 capability。公共文档不应静态宣称某个自定义 Context 可写或可搜索。

如果 Context 返回大型内容,可以配合 ObjectStore 和短期 $ref;签名、有效期与访问边界由宿主实现负责。

本地实例可以把已经注册的 Tool/Context 上报到远程 tool-bridge:

const connection = tb.connect(
'https://tb.example.com',
process.env.TB_DEVICE_SK!,
{ deviceId: 'my-service-01' },
)
const mountPath = await connection.ready
console.log(`mounted at ${mountPath}`)
// 应用退出时:
connection.close()
await connection.closed

长驻服务必须显式使用稳定 deviceId,否则断线重连无法可靠恢复同一个 online 节点。远端 SK 需要 register scope 和相应 registerPaths,详见接入本地设备与服务与权限、SK 与可见性。

SDK 的 connect() 上报注册节点,不内置 CLI 的 shell/fs 执行器。需要暴露 shell 或文件时使用 tb connect,并配置显式白名单。

工具的运行时 metadata 可以把某条命令声明为 delivery: 'mailbox' | 'both',但 connect() 只负责 WebSocket 注册与实时调用,不会自动消费 durable operation。需要离线投递时,从 runtime-neutral 子入口创建 processor:

import { createDeviceMailboxProcessor } from '@tool-bridge/sdk/device'

createDeviceMailboxProcessor 需要同一份 live expose、同一 handler、每次请求现取凭证的 credentialProvider,以及 installation-local 的 durable journal。它只提供 pullOnce() / drain(),不会自行启动 timer 或监听连接状态。宿主必须:

  1. 在初次 WebSocket ready 和每次重连 ready 后触发一次 drain();
  2. 停止、suspend 或进程退出时 abort 当前 drain;
  3. 让 realtime 与 Mailbox 复用同一份命令表和 handler;
  4. 保证 journal put() 在 resolve 前真正持久化,并按 entry expiresAt 回收;
  5. 把非 abort 错误进入宿主日志/事件面,不能把失败当成“已消费”。

journal 是防止重启后重复副作用的执行屏障。handler 开始后崩溃而无法恢复结果时,processor 会提交 result_unknown,不会盲目重跑。内存 Map 不满足这一契约;标准 CLI 使用带权限限制、fsync 与 atomic rename 的本地文件 journal。

完整 delivery 策略、operation 状态与部署前提见Device 离线投递与 Mailbox。

入口 用途
@tool-bridge/sdk Node 22+ 嵌入网关、注册本地函数、Node 反向连接
@tool-bridge/sdk/client Web 标准 HTTP 客户端、固定控制面与运行时解析
@tool-bridge/sdk/store Store 客户端、上传与读取流程
@tool-bridge/sdk/device 实时设备 supervisor、上传助手、显式 Mailbox processor

三个子入口不反向依赖 Node 根入口。它们是客户端能力,不等于部署了一个完整网关。移动端设备需要宿主提供 WebSocket factory、凭证来源和生命周期;Mailbox 还必须有真正 durable journal。

SDK 在首次 fetch/connect 前执行 bootstrap。显式传 adminSk;不要在共享环境随机生成并打印最高权限凭证。

没有自定义 secrets,也没有提供合法 encryption key。显式传 encryptionKey 后重新启动并验证;已有密文还需要原加密根。

确认 SDK 宿主显式提供 mailboxRepository 与合法 encryptionKey,设备侧接入 durable journal 与 processor。只有命令 metadata 声明 delivery,并不代表自定义宿主已经交付离线消费闭环。

本地工具能调用,远程连接后找不到

Section titled “本地工具能调用,远程连接后找不到”

确保在 connect() 前调用 registerTool/registerContext,设备 SK 的 registerPaths 覆盖目标 mountPath,并等待 connection.ready。远程身份看到的最终路径和工具仍以远程 ~help 为准。

SDK 默认不装配完整 built-in catalog。需要自行同源提供 pluginCatalog 和 pluginBindings,或改用标准 Node 宿主。