跳到正文

让 Agent 代表用户调用你的 API ​

本页说明自定义 API 的 on-behalf-of(OBO)链路,以及它与当前 Qoni SDK 产品接口的边界。

当前 SDK 边界

@qoniai/qoni@0.4.1 的具名产品接口覆盖 GUMem 和 Web Agent,并只接受 SDK 已知的产品 scope。它没有公开一个可用任意自定义 resource 与自定义 scope 的端到端具名方法。

因此,不要把 QoniScopes.WEB_SEARCH_*、DO_ANYTHING_* 等 Qoni 产品权限写进“你的 API”示例,也不要把 qoni.webSearch.run() 当成调用自定义 API。自定义 API OBO 需要先在 GenAuth 中配置资源与 scope,再按 HTTP API Reference 完成协议调用。

两条不同的调用路径 ​

目标资源推荐调用方式token 处理
Qoni GUMem / Web Agent使用 qoni.gumem.*、qoni.webSearch.*、qoni.doAnything.*、qoni.deepResearch.* 或 qoni.track.*把 grant.token 传给具名方法;SDK 内部完成产品 token exchange
你的自定义 API使用 GenAuth HTTP 契约和你已配置的 resource / scope兑换出 audience 指向你的 API 的 access token,再由你的 API 验证

这两条路径共享委托、审计和缩权概念,但不能互换代码或 scope。

自定义 API OBO 链路 ​

1. 配置资源与 scope ​

先在 GenAuth 中登记你的 API 资源标识和可委托 scope。资源标识、scope 名称和 token audience 必须来自实际配置,不能从 Qoni Web Agent 常量推断。

2. 发起用户授权 ​

你的服务端通过 GenAuth HTTP API 创建 interactive 委托。请求至少包括:

  • 稳定的 Agent 标识。
  • 你的 API 已登记的 scope。
  • 已登记的 redirectUri。
  • 服务端生成并保存的防重放 state。

interactive 模式由授权页解析当前登录用户,不要额外发送旧版 SDK 的 task、agentKey 或顶层 userId 模型。

3. 完成回调 ​

用户同意后,回调带回一次性 code、你的业务 state 和 grant_state,不带 grantId。按 grant_state 找回创建时保存的记录并核对业务 state,再用保存的 grantId、回调的 code,以及作为 state 传入的 grant_state 完成兑换。委托 token 只能保存在可信服务端。

4. 兑换自定义 API access token ​

按 API Reference 调用 token exchange。请求中的 resource 必须是你的 API 已登记的资源标识,scopes 必须是委托实际授予 scope 的子集。

不要在文档或应用代码中使用 webagent 作为自定义 API 的 resource 占位符;那会把请求导向 Qoni Web Agent,而不是你的服务。

5. 在资源侧验证 ​

你的 API 收到 Authorization: Bearer <access-token> 后至少完成:

  1. 使用可信 JWKS 验证签名。
  2. 校验 iss、aud、exp 和 nbf。
  3. 从 sub 识别被代表的用户,并执行你的业务权限检查。
  4. 从 act 识别执行的 Agent,并写入审计记录。
  5. 校验 scope 覆盖当前操作。
  6. 拒绝 audience、用户、Agent 或 scope 不匹配的请求。

完整资源侧清单见 保护你的 API,claim 结构见 Token 与 Claim 参考。

验收检查 ​

  • 用 read-only scope 调用写接口时,资源侧必须返回拒绝。
  • access token 的 aud 必须是你的 API,而不是 webagent 或其他产品。
  • sub、act、scope、grant ID 和 audit ID 能串成同一条审计链。
  • 用户撤销 grant 后,后续兑换或调用按 GenAuth 的吊销策略失败。

如果你只需要调用 Qoni 产品 ​

不要使用本页的自定义 API 流程。完成 delegateToken() 后,直接把 grant.token 交给具名产品方法:

ts
const search = await qoni.webSearch.run({
  token: grant.token,
  prompt: 'Qoni Agent Identity',
})

const result = await search.wait()

下一步 ​