SDK 参考(@qoniai/qoni)
本页说明 @qoniai/qoni@0.4.1 中与 GenAuth 和委托有关的公开接口。其他语言直接使用 HTTP API。
eak 是 wire 兼容边界
公开包名、构造器和错误类型已经统一为 Qoni;服务端仍保留 /api/v3/eak/* 路径和 eak.* 错误码。使用 SDK 时不要自行拼接这些内部路径。
安装与初始化
npm install @qoniai/qoniimport { Qoni } from '@qoniai/qoni'
const qoni = new Qoni({
accessKey: process.env.QONI_ACCESS_KEY!,
secretKey: process.env.QONI_SECRET_KEY!,
})| 选项 | 类型 | 说明 |
|---|---|---|
accessKey / secretKey | string | 必填。只在可信服务端使用 |
host | string | 可选。私有或本地部署的 Qoni Console / SDK gateway;托管服务通常不传 |
timeoutMs | number | 单次 HTTP 请求超时,默认 30000 |
sseMaxRetries | number | SSE 断线自动重连次数,默认 5,设为 0 关闭 |
fetch | typeof fetch | 自定义 transport |
accessKeyId、accessKeySecret 和各产品直连 Base URL 仍是兼容项,但已弃用。新代码使用上表中的名字,并让 host 完成运行时发现。
delegateToken()
interactive:让当前登录用户确认
交互式委托不传 user。Qoni Console 在授权页中解析当前登录用户,首次调用返回 authorizationUrl,不会返回 token。
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 后再完成兑换:
// 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:
const { data: grant } = await qoni.delegateToken({
mode: 'silent',
user: { id: '<genauth-user-id>' },
agent: 'report-agent',
products: ['webSearch'],
expiresIn: 1800,
})silent 仅适用于已经有组织级授权依据的可信服务端。不要为了省略用户确认而把交互式流程改成 silent。
输入规则
| 字段 | interactive | silent | 说明 |
|---|---|---|---|
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>:
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 / auditId | grant 与审计链 ID |
HTTP 原始响应可能仍使用 delegationToken / delegateAgentToken。SDK 已规范为 token,旧字段只作为弃用兼容别名保留。
silent 响应不提供实际 scope 列表。请使用在线 introspection:
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 两个动词:
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:writeproducts: ['webSearch'] 会展开为 Web Search 的 read + manage。也可以使用 QoniScopeBundles,例如 GUMEM_SESSION_RECALL。不存在 WEB_SEARCH_RUN、DO_ANYTHING_RUN 或 AnimaScopes。
命名空间
| Namespace | 当前公开方法 |
|---|---|
qoni.genauth | userInfo、jwks、discovery、introspectDelegationToken、users.* |
qoni.gumem | createSession、addMessages、recall、uploadResource、actions.* |
qoni.webSearch | run、attach |
qoni.doAnything | run、attach |
qoni.deepResearch | run、attach |
qoni.track | create、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。
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。