跳到正文

SDK 参考(@qoniai/qoni) ​

本页说明 @qoniai/qoni@0.4.1 中与 GenAuth 和委托有关的公开接口。其他语言直接使用 HTTP API。

eak 是 wire 兼容边界

公开包名、构造器和错误类型已经统一为 Qoni;服务端仍保留 /api/v3/eak/* 路径和 eak.* 错误码。使用 SDK 时不要自行拼接这些内部路径。

安装与初始化 ​

bash
npm install @qoniai/qoni
ts
import { Qoni } from '@qoniai/qoni'

const qoni = new Qoni({
  accessKey: process.env.QONI_ACCESS_KEY!,
  secretKey: process.env.QONI_SECRET_KEY!,
})
选项类型说明
accessKey / secretKeystring必填。只在可信服务端使用
hoststring可选。私有或本地部署的 Qoni Console / SDK gateway;托管服务通常不传
timeoutMsnumber单次 HTTP 请求超时,默认 30000
sseMaxRetriesnumberSSE 断线自动重连次数,默认 5,设为 0 关闭
fetchtypeof fetch自定义 transport

accessKeyId、accessKeySecret 和各产品直连 Base URL 仍是兼容项,但已弃用。新代码使用上表中的名字,并让 host 完成运行时发现。

delegateToken() ​

interactive:让当前登录用户确认 ​

交互式委托不传 user。Qoni Console 在授权页中解析当前登录用户,首次调用返回 authorizationUrl,不会返回 token。

ts
import { QoniScopes } from '@qoniai/qoni'

const businessState = crypto.randomUUID()
const { data: authorization } = await qoni.delegateToken({
  mode: 'interactive',
  agent: 'report-agent',
  scopes: [QoniScopes.WEB_SEARCH_READ, QoniScopes.WEB_SEARCH_MANAGE],
  redirectUri: 'https://yourapp.example.com/qoni/callback',
  state: businessState,
  expiresIn: 1800,
})

const authorizationUrl = new URL(authorization.authorizationUrl)
if (authorizationUrl.searchParams.get('grant_id') !== authorization.grantId) {
  throw new Error('Invalid delegation grant')
}
// 回调不会带回 grantId:按 grantState 保存,回调时用 grant_state 找回
await savePendingGrant(authorization.grantState, {
  grantId: authorization.grantId,
  businessState,
})
console.log(authorization.authorizationUrl)

响应 state 是服务端 grantState 的兼容别名,不会回显 businessState。回调只带 code、业务 state 和 grant_state,不带 grantId。应用必须在回调中按 grant_state 读取当前用户 Session 绑定的 pending grant,核对业务 state 后再完成兑换:

ts
// callback 是回调查询参数:code、state(businessState)、grant_state
const pending = await loadPendingGrant(callback.grant_state)
if (!pending || callback.state !== pending.businessState) throw new Error('Invalid delegation callback')
const { data: grant } = await qoni.completeDelegateToken({
  grantId: pending.grantId,
  code: callback.code,
  state: callback.grant_state,
})
console.log(grant.token)

silent:受信服务端路径 ​

静默委托不会经过授权页,因此必须传入与当前 Qoni 凭证绑定的 GenAuth 用户 ID:

ts
const { data: grant } = await qoni.delegateToken({
  mode: 'silent',
  user: { id: '<genauth-user-id>' },
  agent: 'report-agent',
  products: ['webSearch'],
  expiresIn: 1800,
})

silent 仅适用于已经有组织级授权依据的可信服务端。不要为了省略用户确认而把交互式流程改成 silent。

输入规则 ​

字段interactivesilent说明
mode必须为 interactive可省略,默认 silent委托模式
user不需要必填推荐形状为 { id: string };顶层 userId 已弃用
agent可选可选审计标签,未传时默认 sdk
scopes与 products 至少一个与 products 至少一个细粒度 scope
products与 scopes 至少一个与 scopes 至少一个doAnything、webSearch、deepResearch、track 的权限简写
redirectUri / state必填可选授权回调与防重放 state
expiresIn / idempotencyKey可选可选有效期与幂等键

委托响应 ​

SDK 统一使用 QoniResponse<T>:

ts
type QoniResponse<T> = {
  data: T
  meta: {
    requestId?: string
    traceId?: string
    auditId?: string
    service?: 'qoni' | 'genauth' | 'gumem' | 'webagent'
  }
}

completeDelegateToken() 和 silent delegateToken() 的 data 使用以下主要字段:

字段说明
token后续 GUMem / Web Agent 调用使用的委托 token
tokenType当前为 Bearer
expiresIn有效期(秒)
grantId / auditIdgrant 与审计链 ID

HTTP 原始响应可能仍使用 delegationToken / delegateAgentToken。SDK 已规范为 token,旧字段只作为弃用兼容别名保留。

silent 响应不提供实际 scope 列表。请使用在线 introspection:

ts
const { data: info } = await qoni.genauth.introspectDelegationToken({ token })
if (!info.active) throw new Error('Delegation token is inactive')
const effectiveScopes = info.scope ?? []

interactive 响应中的 state 是服务端生成 grantState 的兼容别名,不是调用方 state 的回显。用 authorizationUrl 中的 grant_id === grantId 校验关联,并在服务端按 grantState 保存 grantId(回调不带 grantId)。

Scope 常量与产品简写 ​

每个 Web Agent 产品只有 read 和 manage 两个动词:

ts
QoniScopes.WEB_SEARCH_READ       // webagent.web_search:read
QoniScopes.WEB_SEARCH_MANAGE     // webagent.web_search:manage
QoniScopes.DO_ANYTHING_READ      // webagent.do_anything:read
QoniScopes.DO_ANYTHING_MANAGE    // webagent.do_anything:manage
QoniScopes.GUMEM_MEMORY_READ     // gumem.memory:read
QoniScopes.GUMEM_MEMORY_WRITE    // gumem.memory:write

products: ['webSearch'] 会展开为 Web Search 的 read + manage。也可以使用 QoniScopeBundles,例如 GUMEM_SESSION_RECALL。不存在 WEB_SEARCH_RUN、DO_ANYTHING_RUN 或 AnimaScopes。

命名空间 ​

Namespace当前公开方法
qoni.genauthuserInfo、jwks、discovery、introspectDelegationToken、users.*
qoni.gumemcreateSession、addMessages、recall、uploadResource、actions.*
qoni.webSearchrun、attach
qoni.doAnythingrun、attach
qoni.deepResearchrun、attach
qoni.trackcreate、attach

qoni.currentUser({ accessToken }) 用 GenAuth access token 读取当前用户;resolveAnyBoundUser() 只适合 demo 和冒烟测试。request() / unstableRequest() 是 wire-level escape hatch,不应用来代替已经存在的具名产品方法。SDK 会在产品调用时完成所需的内部 token exchange。

错误处理 ​

所有 SDK 错误继承 QoniError,可读取 code、status、requestId、traceId、auditId 和 retryable。

ts
import { QoniPermissionDeniedError, QoniTokenExpiredError } from '@qoniai/qoni'

try {
  await qoni.webSearch.run({ token: grant.token, prompt: 'Qoni SDK' })
} catch (error) {
  if (error instanceof QoniPermissionDeniedError) {
    // 重新申请缺少的 scope
  }
  if (error instanceof QoniTokenExpiredError) {
    // 重新发起委托
  }
  throw error
}

完整错误类包括 QoniValidationError、QoniAuthError、QoniPermissionDeniedError、QoniTokenExpiredError、QoniDelegationRequiredError、QoniRateLimitError、QoniTimeoutError 和 QoniUpstreamError。

下一步 ​