@qoniai/qoni 0.9.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +62 -0
- package/README.md +132 -38
- package/README.zh-CN.md +121 -34
- package/dist/index.cjs +13842 -492
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +25288 -161
- package/dist/index.d.ts +25288 -161
- package/dist/index.js +13840 -492
- package/dist/index.js.map +1 -1
- package/docs/authing-sdk-LICENSE.txt +21 -0
- package/docs/genauth-communication.md +1410 -0
- package/docs/genauth-management-api.md +597 -0
- package/docs/genauth-management.md +285 -0
- package/package.json +14 -5
- package/skills/qoni-sdk/SKILL.md +114 -3
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: GenAuth 管理 SDK
|
|
3
|
+
description: 初始化 SDK,管理用户、用户组、组织、角色、权限、应用和自定义字段。
|
|
4
|
+
status: draft
|
|
5
|
+
sdkCandidate: contracts/genauth-management.json
|
|
6
|
+
lastReviewed: 2026-10-12
|
|
7
|
+
pageClass: qoni-sdk-management
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 初始化
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import { Qoni } from '@qoniai/qoni'
|
|
14
|
+
|
|
15
|
+
const qoni = new Qoni({
|
|
16
|
+
accessKey: process.env.QONI_ACCESS_KEY!,
|
|
17
|
+
secretKey: process.env.QONI_SECRET_KEY!,
|
|
18
|
+
})
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
使用绑定目标 GenAuth 用户池的 Qoni 访问密钥,在服务端调用。
|
|
22
|
+
|
|
23
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
24
|
+
| --- | --- | --- | --- |
|
|
25
|
+
| `accessKey` | `string` | 是 | Qoni 访问密钥 |
|
|
26
|
+
| `secretKey` | `string` | 是 | Qoni 访问密钥 Secret |
|
|
27
|
+
| `host` | `string` | 否 | 私有部署的 Qoni 服务地址 |
|
|
28
|
+
| `genauthHost` | `string` | 否 | 指定 GenAuth 服务地址 |
|
|
29
|
+
| `timeoutMs` | `number` | 否 | 请求超时,单位毫秒,默认 `30000` |
|
|
30
|
+
| `fetch` | `typeof fetch` | 否 | 自定义 HTTP transport |
|
|
31
|
+
|
|
32
|
+
## 用户管理
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
const { data: user } = await qoni.genauth.users.create({
|
|
36
|
+
username: 'sdk-example-user',
|
|
37
|
+
name: 'Example user',
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
const { data: userDetails } = await qoni.genauth.users.get({ userId: user.userId })
|
|
41
|
+
await qoni.genauth.users.update({ userId: user.userId, nickname: 'Alice' })
|
|
42
|
+
const { data: userBatch } = await qoni.genauth.users.getBatch({ userIds: [user.userId] })
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
| 方法 | 功能 | 主要参数 | `data` |
|
|
46
|
+
| --- | --- | --- | --- |
|
|
47
|
+
| `users.create` | 创建用户 | username / email / phone;name、password 等可选 | 用户对象 |
|
|
48
|
+
| `users.get` | 读取用户 | userId | 用户对象 |
|
|
49
|
+
| `users.getBatch` | 批量读取用户 | userIds: string[] | 用户数组 |
|
|
50
|
+
| `users.update` | 更新用户 | userId 与需要更新的字段 | 更新后的用户 |
|
|
51
|
+
| `users.createBatch` | 批量创建用户 | list: 用户参数数组 | 用户数组 |
|
|
52
|
+
| `users.deleteBatch` | 批量删除用户 | userIds: string[] | 删除结果 |
|
|
53
|
+
|
|
54
|
+
创建时至少设置用户名、邮箱或手机号。删除在职用户前,先按业务流程通过 `users.resign({ userId })` 设置离职状态。
|
|
55
|
+
|
|
56
|
+
## 用户查询与分页
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
const { data: userPage } = await qoni.genauth.users.list({
|
|
60
|
+
page: 1,
|
|
61
|
+
limit: 20,
|
|
62
|
+
keywords: 'alice',
|
|
63
|
+
options: { fuzzySearchOn: ['username', 'email'] },
|
|
64
|
+
})
|
|
65
|
+
console.log(userPage.list, userPage.totalCount)
|
|
66
|
+
|
|
67
|
+
for await (const item of qoni.genauth.users.iterate({ limit: 50 })) {
|
|
68
|
+
console.log(item.userId)
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
| 方法 | 功能 | 主要参数 | `data` |
|
|
73
|
+
| --- | --- | --- | --- |
|
|
74
|
+
| `users.list` | 分页查询用户 | page、limit、keywords、options | { list, totalCount } |
|
|
75
|
+
| `users.iterate` | 逐个遍历用户 | page 默认 1,limit 默认 50、范围 1–50 | `AsyncIterable<User>` |
|
|
76
|
+
|
|
77
|
+
`page` 从 1 开始。`keywords` 用于关键字筛选,`options.fuzzySearchOn` 指定匹配字段。无需筛选时省略这两个参数。`iterate` 中使用 `break` 停止遍历。
|
|
78
|
+
|
|
79
|
+
## 用户组
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
const { data: group } = await qoni.genauth.groups.create({
|
|
83
|
+
code: 'engineering',
|
|
84
|
+
name: 'Engineering',
|
|
85
|
+
})
|
|
86
|
+
await qoni.genauth.groups.addMembers({ code: group.code, userIds: [user.userId] })
|
|
87
|
+
const { data: groupPage } = await qoni.genauth.groups.list({ page: 1, limit: 20 })
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
| 方法 | 功能 | 主要参数 | `data` |
|
|
91
|
+
| --- | --- | --- | --- |
|
|
92
|
+
| `groups.create` | 创建用户组 | code、name | 用户组对象 |
|
|
93
|
+
| `groups.get` | 读取用户组 | code | 用户组对象 |
|
|
94
|
+
| `groups.list` | 查询用户组 | page、limit | { list, totalCount } |
|
|
95
|
+
| `groups.update` | 更新用户组 | code 与更新字段 | 用户组对象 |
|
|
96
|
+
| `groups.addMembers` | 添加成员 | code、userIds | 操作结果 |
|
|
97
|
+
|
|
98
|
+
## 组织与部门
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
await qoni.genauth.organizations.create({
|
|
102
|
+
organizationCode: 'company',
|
|
103
|
+
organizationName: 'Example company',
|
|
104
|
+
})
|
|
105
|
+
const { data: department } = await qoni.genauth.departments.create({
|
|
106
|
+
organizationCode: 'company',
|
|
107
|
+
parentDepartmentId: 'root',
|
|
108
|
+
name: 'Engineering',
|
|
109
|
+
code: 'engineering',
|
|
110
|
+
})
|
|
111
|
+
await qoni.genauth.departments.addMembers({
|
|
112
|
+
organizationCode: 'company',
|
|
113
|
+
departmentId: department.departmentId,
|
|
114
|
+
userIds: [user.userId],
|
|
115
|
+
})
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
| 方法 | 功能 | 主要参数 | `data` |
|
|
119
|
+
| --- | --- | --- | --- |
|
|
120
|
+
| `organizations.create` | 创建组织 | organizationCode、organizationName | 组织对象 |
|
|
121
|
+
| `organizations.get` | 读取组织 | organizationCode | 组织对象 |
|
|
122
|
+
| `organizations.list` | 查询组织 | page、limit | 组织列表 |
|
|
123
|
+
| `organizations.update` | 更新组织 | organizationCode 与更新字段 | 组织对象 |
|
|
124
|
+
| `departments.create` | 创建部门 | organizationCode、parentDepartmentId、name | 部门对象 |
|
|
125
|
+
| `departments.get` | 读取部门 | organizationCode、departmentId 或 departmentCode | 部门对象 |
|
|
126
|
+
| `departments.update` | 更新部门 | organizationCode、departmentId 与更新字段 | 部门对象 |
|
|
127
|
+
| `departments.addMembers` | 添加部门成员 | organizationCode、departmentId、userIds | 操作结果 |
|
|
128
|
+
|
|
129
|
+
根部门的 `parentDepartmentId` 使用 `root`;其他部门使用实际父部门 ID。
|
|
130
|
+
|
|
131
|
+
## 角色、资源与授权
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
await qoni.genauth.namespaces.create({ code: 'project', name: 'Project' })
|
|
135
|
+
await qoni.genauth.roles.create({ namespace: 'project', code: 'reader', name: 'Reader' })
|
|
136
|
+
await qoni.genauth.roles.assign({
|
|
137
|
+
namespace: 'project',
|
|
138
|
+
code: 'reader',
|
|
139
|
+
targets: [{ targetType: 'USER', targetIdentifier: user.userId }],
|
|
140
|
+
})
|
|
141
|
+
await qoni.genauth.resources.create({
|
|
142
|
+
namespace: 'project', code: 'documents', type: 'API',
|
|
143
|
+
apiIdentifier: '/documents',
|
|
144
|
+
actions: [{ name: 'documents:read', description: 'Read documents' }],
|
|
145
|
+
})
|
|
146
|
+
await qoni.genauth.permissions.authorize({
|
|
147
|
+
namespace: 'project',
|
|
148
|
+
list: [{
|
|
149
|
+
targetType: 'ROLE', targetIdentifiers: ['reader'],
|
|
150
|
+
resources: [{ code: 'documents:*', resourceType: 'API', actions: ['documents:read'] }],
|
|
151
|
+
}],
|
|
152
|
+
})
|
|
153
|
+
const { data: permission } = await qoni.genauth.permissions.isAllowed({
|
|
154
|
+
namespace: 'project', userId: user.userId,
|
|
155
|
+
resource: 'documents:123', action: 'documents:read',
|
|
156
|
+
})
|
|
157
|
+
console.log(permission.allowed)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
| 方法 | 功能 | 主要参数 | `data` |
|
|
161
|
+
| --- | --- | --- | --- |
|
|
162
|
+
| `namespaces.create` | 创建权限空间 | code、name | 权限空间对象 |
|
|
163
|
+
| `roles.create` | 创建角色 | namespace、code、name | 角色对象 |
|
|
164
|
+
| `roles.list` | 查询角色 | namespace、page、limit | { list, totalCount } |
|
|
165
|
+
| `roles.assign` | 分配角色 | namespace、code、targets | 操作结果 |
|
|
166
|
+
| `resources.create` | 定义资源及动作 | namespace、code、type、actions;API 资源使用 apiIdentifier | 资源对象 |
|
|
167
|
+
| `permissions.authorize` | 授予资源权限 | namespace、list | 操作结果 |
|
|
168
|
+
| `permissions.isAllowed` | 判断用户权限 | namespace、userId、resource、action | { allowed } |
|
|
169
|
+
|
|
170
|
+
角色、资源和授权使用同一 `namespace`。创建资源用 `type`,授权资源项用 `resourceType`。
|
|
171
|
+
|
|
172
|
+
## 应用管理
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
const { data: application } = await qoni.genauth.applications.create({
|
|
176
|
+
appName: 'Example app',
|
|
177
|
+
appIdentifier: 'example-app',
|
|
178
|
+
appType: 'web',
|
|
179
|
+
defaultProtocol: 'oidc',
|
|
180
|
+
redirectUris: ['https://app.example.com/callback'],
|
|
181
|
+
})
|
|
182
|
+
const { data: appDetails } = await qoni.genauth.applications.get({ appId: application.appId })
|
|
183
|
+
const { data: appPage } = await qoni.genauth.applications.list({ page: 1, limit: 20 })
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
| 方法 | 功能 | 主要参数 | `data` |
|
|
187
|
+
| --- | --- | --- | --- |
|
|
188
|
+
| `applications.create` | 创建应用 | appName;自建应用传 appIdentifier,可设置 appType、defaultProtocol、redirectUris | 应用创建结果 |
|
|
189
|
+
| `applications.get` | 读取应用配置 | appId | 应用对象 |
|
|
190
|
+
| `applications.list` | 分页查询应用 | page、limit、keywords | 应用列表 |
|
|
191
|
+
| `applications.update` | 更新应用配置 | appId 与更新字段 | 应用对象 |
|
|
192
|
+
| `applications.delete` | 删除应用 | appId | 操作结果 |
|
|
193
|
+
|
|
194
|
+
## 自定义字段
|
|
195
|
+
|
|
196
|
+
```ts
|
|
197
|
+
await qoni.genauth.customFields.set({
|
|
198
|
+
list: [{ targetType: 'USER', key: 'costCenter', label: 'Cost center', dataType: 'STRING' }],
|
|
199
|
+
})
|
|
200
|
+
await qoni.genauth.customFields.setData({
|
|
201
|
+
targetType: 'USER', targetIdentifier: user.userId,
|
|
202
|
+
list: [{ key: 'costCenter', value: 'engineering' }],
|
|
203
|
+
})
|
|
204
|
+
const { data: fieldValues } = await qoni.genauth.customFields.getData({
|
|
205
|
+
targetType: 'USER', targetIdentifier: user.userId,
|
|
206
|
+
})
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
| 方法 | 功能 | 主要参数 | `data` |
|
|
210
|
+
| --- | --- | --- | --- |
|
|
211
|
+
| `customFields.list` | 查询字段定义 | targetType,默认 USER | 字段定义列表 |
|
|
212
|
+
| `customFields.set` | 创建或更新字段定义 | list: { targetType, key, label, dataType }[] | 操作结果 |
|
|
213
|
+
| `customFields.setData` | 设置对象字段值 | targetType、targetIdentifier、list | 操作结果 |
|
|
214
|
+
| `customFields.getData` | 读取对象字段值 | targetType、targetIdentifier | 字段数据 |
|
|
215
|
+
|
|
216
|
+
先定义字段,再写入字段值。已有字段的 `dataType` 不能修改;角色字段需要传所属 `namespace`。
|
|
217
|
+
|
|
218
|
+
## 其他管理功能
|
|
219
|
+
|
|
220
|
+
| 入口 | 功能 |
|
|
221
|
+
| --- | --- |
|
|
222
|
+
| `tenants` | 租户与租户成员 |
|
|
223
|
+
| `identityProviders` | 外部身份源与连接配置 |
|
|
224
|
+
| `dataPermissions / policies` | 数据资源、策略及数据授权 |
|
|
225
|
+
| `auditLogs / sessions` | 管理审计与用户会话 |
|
|
226
|
+
| `security / whitelist / riskLists` | MFA、安全设置及访问名单 |
|
|
227
|
+
| `webhooks / events` | Webhook 配置与事件 |
|
|
228
|
+
| `sync / pipelines` | 账号同步及认证流程 |
|
|
229
|
+
| `accessKeys` | 访问密钥管理 |
|
|
230
|
+
| `positions / dynamicGroups` | 岗位与动态用户组 |
|
|
231
|
+
| `devices / deviceRules` | 设备及设备规则 |
|
|
232
|
+
| `publicAccounts / invitations` | 公共账号及邀请 |
|
|
233
|
+
| `approvals / ldap` | 审批与 LDAP 配置 |
|
|
234
|
+
| `metadata / email / billing` | 元数据、邮件及计费配置 |
|
|
235
|
+
|
|
236
|
+
以上入口均位于 `qoni.genauth` 下。具体方法、参数与返回类型见 [管理方法参考](./genauth-management-api)。
|
|
237
|
+
|
|
238
|
+
## 返回值与请求取消
|
|
239
|
+
|
|
240
|
+
```ts
|
|
241
|
+
const controller = new AbortController()
|
|
242
|
+
const { data: rolePage, meta } = await qoni.genauth.roles.list(
|
|
243
|
+
{ namespace: 'project', page: 1, limit: 20 },
|
|
244
|
+
{ signal: controller.signal },
|
|
245
|
+
)
|
|
246
|
+
console.log(rolePage.list, meta.requestId)
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
方法返回 `Promise<QoniResponse<T>>`:`data` 为业务结果,`meta.requestId` 为请求标识。第二参数传 `{ signal }`,调用 `controller.abort()` 取消请求。`users.iterate` 返回 `AsyncIterable`。
|
|
250
|
+
|
|
251
|
+
## 使用已有管理 Token
|
|
252
|
+
|
|
253
|
+
```ts
|
|
254
|
+
const { data: roleList } = await qoni.genauth.roles.list({
|
|
255
|
+
namespace: 'project',
|
|
256
|
+
adminToken: process.env.GENAUTH_MANAGEMENT_TOKEN!,
|
|
257
|
+
userPoolId: process.env.GENAUTH_USER_POOL_ID!,
|
|
258
|
+
})
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
`adminToken` 与 `userPoolId` 必须同时传入。显式传入的 Token 过期后由调用方更新。
|
|
262
|
+
|
|
263
|
+
## 错误处理
|
|
264
|
+
|
|
265
|
+
```ts
|
|
266
|
+
import { QoniError } from '@qoniai/qoni'
|
|
267
|
+
|
|
268
|
+
try {
|
|
269
|
+
await qoni.genauth.users.get({ userId: '<user-id>' })
|
|
270
|
+
} catch (error) {
|
|
271
|
+
if (error instanceof QoniError) {
|
|
272
|
+
console.error(error.code, error.message, error.requestId)
|
|
273
|
+
}
|
|
274
|
+
throw error
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
| 错误 | 处理方式 |
|
|
279
|
+
| --- | --- |
|
|
280
|
+
| `QoniValidationError` | 检查必填参数与字段类型 |
|
|
281
|
+
| `401` / `403` | 检查凭据、用户池绑定及管理权限 |
|
|
282
|
+
| `404` | 检查对象 ID 与所属用户池 |
|
|
283
|
+
| `409` | 处理对象状态冲突或标识重复 |
|
|
284
|
+
| `QoniTimeoutError` | 重试写操作前先查询原操作结果 |
|
|
285
|
+
| 其他 `QoniError` | 查看 `code`、`message`、`requestId`、`body` |
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qoniai/qoni",
|
|
3
|
-
"version": "0.9.
|
|
4
|
-
"description": "Unified Node.js SDK for Qoni Agent delegation, GUMem memory, and WebAgent automation.",
|
|
3
|
+
"version": "0.9.1",
|
|
4
|
+
"description": "Unified Node.js SDK for Qoni Agent delegation, GenAuth management, GUMem memory, and WebAgent automation.",
|
|
5
5
|
"homepage": "https://github.com/QONIAI/qoni-sdk-node#readme",
|
|
6
6
|
"bugs": {
|
|
7
7
|
"url": "https://github.com/QONIAI/qoni-sdk-node/issues"
|
|
@@ -19,7 +19,10 @@
|
|
|
19
19
|
"sdk",
|
|
20
20
|
"delegation",
|
|
21
21
|
"gumem",
|
|
22
|
-
"webagent"
|
|
22
|
+
"webagent",
|
|
23
|
+
"genauth",
|
|
24
|
+
"authing",
|
|
25
|
+
"management"
|
|
23
26
|
],
|
|
24
27
|
"main": "./dist/index.cjs",
|
|
25
28
|
"module": "./dist/index.js",
|
|
@@ -46,7 +49,7 @@
|
|
|
46
49
|
"gen:event-types": "node scripts/gen-event-types.mjs",
|
|
47
50
|
"typecheck": "tsc --noEmit",
|
|
48
51
|
"test": "pnpm build && pnpm check:brand && pnpm test:mock && pnpm test:package",
|
|
49
|
-
"test:mock": "vitest run --coverage=false tests/qoni.test.ts tests/semantic-surface.test.ts tests/open-source-readiness.test.ts tests/complete-coverage.test.ts tests/event-catalog.test.ts tests/interactions.test.ts tests/contract-surface.test.ts tests/sse-abort.test.ts tests/do-anything-evidence.test.ts tests/query-matrix.test.ts tests/live-evidence.test.ts tests/site-login-settle-track.test.ts tests/abstract-interactions.test.ts",
|
|
52
|
+
"test:mock": "vitest run --coverage=false tests/qoni.test.ts tests/semantic-surface.test.ts tests/open-source-readiness.test.ts tests/complete-coverage.test.ts tests/event-catalog.test.ts tests/interactions.test.ts tests/contract-surface.test.ts tests/sse-abort.test.ts tests/do-anything-evidence.test.ts tests/query-matrix.test.ts tests/live-evidence.test.ts tests/site-login-settle-track.test.ts tests/abstract-interactions.test.ts tests/genauth-management.test.ts tests/genauth-communication.test.ts tests/genauth-hosted-login.test.ts tests/personal-agent-page.test.ts tests/agent-provisioning.test.ts tests/formal-agent-silent.test.ts tests/formal-agent-wildcard.test.ts tests/delegation-normalizer-r3.test.ts tests/gray-track-contract.test.ts",
|
|
50
53
|
"test:e2e": "QONI_LIVE_E2E=1 vitest run --coverage=false --fileParallelism=false --maxWorkers=1 --testTimeout=120000 tests/live-e2e/*.test.ts",
|
|
51
54
|
"test:e2e:do-anything": "QONI_LIVE_E2E=1 vitest run --coverage=false --fileParallelism=false --maxWorkers=1 --testTimeout=120000 tests/live-e2e/do-anything.test.ts",
|
|
52
55
|
"test:e2e:do-anything:release": "QONI_LIVE_E2E=1 QONI_LIVE_ALLOW_ENV_CONSTRAINTS=0 vitest run --coverage=false --fileParallelism=false --maxWorkers=1 --testTimeout=120000 tests/live-e2e/do-anything.test.ts",
|
|
@@ -58,7 +61,13 @@
|
|
|
58
61
|
"smoke:auth-gum:live": "pnpm build && QONI_SMOKE_MODE=live node scripts/qoni-auth-gum-smoke.mjs --live",
|
|
59
62
|
"pack:dry-run": "pnpm build && pnpm pack --dry-run",
|
|
60
63
|
"prepack": "pnpm build",
|
|
61
|
-
"prepare": "tsup"
|
|
64
|
+
"prepare": "tsup",
|
|
65
|
+
"gen:genauth-management": "node scripts/gen-genauth-management.mjs",
|
|
66
|
+
"test:e2e:genauth": "QONI_LIVE_E2E=1 vitest run --coverage=false --fileParallelism=false --maxWorkers=1 --testTimeout=30000 tests/live-e2e/genauth-management.test.ts",
|
|
67
|
+
"gen:genauth-communication": "node scripts/gen-genauth-communication.mjs",
|
|
68
|
+
"check:genauth-communication": "node scripts/gen-genauth-communication.mjs --check",
|
|
69
|
+
"test:e2e:communication": "QONI_LIVE_E2E=1 vitest run --coverage=false --fileParallelism=false --maxWorkers=1 --testTimeout=30000 tests/live-e2e/genauth-communication.test.ts",
|
|
70
|
+
"check:genauth-communication-examples": "node scripts/check-genauth-communication-examples.mjs"
|
|
62
71
|
},
|
|
63
72
|
"publishConfig": {
|
|
64
73
|
"access": "public"
|
package/skills/qoni-sdk/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: qoni-sdk
|
|
3
|
-
description: Use when building, testing, debugging, or documenting Node.js integrations with @qoniai/qoni, the Qoni constructor, AK/SK credentials, delegateToken, GenAuth currentUser/userInfo/introspection/
|
|
3
|
+
description: Use when building, testing, debugging, or documenting Node.js integrations with @qoniai/qoni, the Qoni constructor, AK/SK credentials, delegateToken, GenAuth currentUser/userInfo/introspection/Authing v3 management, Qoni runtime-config discovery, GUMem, WebAgent, Web Search, Do Anything, Deep Research, Track, OBO product token exchange, or local .genauth.localhost SDK flows.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Qoni SDK
|
|
@@ -8,14 +8,14 @@ description: Use when building, testing, debugging, or documenting Node.js integ
|
|
|
8
8
|
> **Requires `@qoniai/qoni` ≥ 0.4.0.** This guide documents the semantic-layer
|
|
9
9
|
> surface: `run()`/`create()` return handles (`RunHandle` / `MonitorHandle`),
|
|
10
10
|
> events are normalized (`event.type`, camelCase `data`, decoded screenshots),
|
|
11
|
-
> and
|
|
11
|
+
> and supported 1:1 wire methods live under `qoni.<product>.api.*`. If the consumer
|
|
12
12
|
> resolves an older version from npm, install this repo's local build instead
|
|
13
13
|
> (`npm install <path-to-qoni-sdk-node>` or a `pnpm pack` tarball) — otherwise
|
|
14
14
|
> the code below will not match the installed SDK.
|
|
15
15
|
|
|
16
16
|
## Core Contract
|
|
17
17
|
|
|
18
|
-
Use `@qoniai/qoni` from trusted server-side Node.js code. Keep AK/SK on the server and create one reusable `Qoni`. For silent runtime product calls, resolve a real GenAuth user id from the credential-bound userpool, call `delegateToken`, then pass `delegation.data.token` explicitly to every product capability call. For interactive runtime authorization, call `delegateToken({ mode: "interactive", redirectUri, state, agent, scopes })` from the server without exposing AK/SK; redirect the user to `authorizationUrl`, then handle the business callback server-side with `completeDelegateToken({ grantId, code, state })` to receive the token. For GenAuth
|
|
18
|
+
Use `@qoniai/qoni` from trusted server-side Node.js code. Keep AK/SK on the server and create one reusable `Qoni`. For silent runtime product calls, resolve a real GenAuth user id from the credential-bound userpool, call `delegateToken`, then pass `delegation.data.token` explicitly to every product capability call. For interactive runtime authorization, call `delegateToken({ mode: "interactive", redirectUri, state, agent, scopes })` from the server without exposing AK/SK; redirect the user to `authorizationUrl`, then handle the business callback server-side with `completeDelegateToken({ grantId, code, state })` to receive the token. For GenAuth management, use `qoni.genauth` domain namespaces (such as `users`, `roles`, `groups`, `organizations`, `applications`) or the Authing-name migration entry `qoni.genauth.management`; the SDK exchanges AK/SK for a standard GenAuth management token internally and does not require `QONI_USER_ID`.
|
|
19
19
|
|
|
20
20
|
```ts
|
|
21
21
|
import { Qoni, QoniScopes } from "@qoniai/qoni";
|
|
@@ -215,6 +215,101 @@ for await (const event of run.events()) {
|
|
|
215
215
|
|
|
216
216
|
`qoni.doAnything.api.events({ token, sessionId, runId, signal?, lastEventId?, onlyTopLevel? })` yields raw `{ id?, event, data }` wire events: the type is a raw string on `event.event` (e.g. `"run.status_changed"`), the payload is `event.data.data` (snake_case), and `event.data.task_id` identifies the run. `run.status_changed` payloads carry the new status in `.to` (NOT `.status`). Terminal detection is `event.event === "run.completed"`. Wire names are internal and may evolve — prefer the high-level `run.events()` + `event.type` instead.
|
|
217
217
|
|
|
218
|
+
## Track and deprecated snapshot reads
|
|
219
|
+
|
|
220
|
+
Use `track.create({ token, prompt, schedule, title? })` with an explicit
|
|
221
|
+
`{ kind: "interval", intervalSeconds: N }` (integer 600–2592000 seconds) or
|
|
222
|
+
`{ kind: "daily", at: "HH:MM", tz: "Area/City" }`. Daily starts 0–30 minutes
|
|
223
|
+
before the target. Prompt maps to instructions; include URLs there. No default
|
|
224
|
+
schedule is inferred, and arbitrary cron is rejected with a daily alternative.
|
|
225
|
+
|
|
226
|
+
The historical `MonitorHandle` name remains. `get()` normalizes the real Track
|
|
227
|
+
record: known schedule shapes convert without write-bound/Intl revalidation;
|
|
228
|
+
unknown/incomplete wire schedules stay verbatim. Input validation remains strict.
|
|
229
|
+
`pause()`/`resume()` PATCH status; pause keeps any started check running.
|
|
230
|
+
`refine()` accepts title/schedule only and cannot change instructions.
|
|
231
|
+
`runNow()` returns `{ trackId, sessionId, runId }` (202 acceptance); it can
|
|
232
|
+
surface 409 task_in_progress. `runs({ limit?, offset? })` slices the newest 50
|
|
233
|
+
checks locally; `run(id)` searches that exact window and reports its range on
|
|
234
|
+
not_found. Keep real status/outcome/headline/answer/failure, never invent
|
|
235
|
+
extracted/diffs/snapshotId. `delete()` soft-deletes without cancelling a running task.
|
|
236
|
+
|
|
237
|
+
Create starts a first_look immediately. Capture the original initial lastTaskId,
|
|
238
|
+
wait for that exact check to terminate, then call runNow once; never retry 409
|
|
239
|
+
implicitly or substitute another check. Checks keep `running/done/failed/expired/
|
|
240
|
+
canceled`; success is done, distinct from RunResult.status succeeded. A missing
|
|
241
|
+
check in the recent window is local not_found without an HTTP status; a missing
|
|
242
|
+
Track is backend 404 track_not_found.
|
|
243
|
+
|
|
244
|
+
The example uses the reusable qoni client and delegated token from Core Contract:
|
|
245
|
+
|
|
246
|
+
```ts
|
|
247
|
+
import type { MonitorSchedule } from "@qoniai/qoni";
|
|
248
|
+
|
|
249
|
+
const monitor = await qoni.track.create({
|
|
250
|
+
token,
|
|
251
|
+
prompt: "Watch https://example.com/policy and report changes.",
|
|
252
|
+
title: "Policy changes",
|
|
253
|
+
schedule: { kind: "interval", intervalSeconds: 3600 },
|
|
254
|
+
}); // create immediately starts a first_look check
|
|
255
|
+
const initial = await monitor.get();
|
|
256
|
+
const firstRunId = initial.lastTaskId; // capture this original ID once
|
|
257
|
+
if (typeof firstRunId !== "string" || !firstRunId) throw new Error("Missing initial lastTaskId");
|
|
258
|
+
const sameMonitor = await qoni.track.attach(monitor.id, { token });
|
|
259
|
+
|
|
260
|
+
async function waitForCheck(runId: string) {
|
|
261
|
+
const deadline = Date.now() + 300_000;
|
|
262
|
+
do {
|
|
263
|
+
const check = await monitor.run(runId); // exact ID in the recent 50 checks
|
|
264
|
+
if (["done", "failed", "expired", "canceled"].includes(String(check.status))) {
|
|
265
|
+
if (check.status !== "done") throw new Error(`Check ${runId} ${String(check.status)}: ${String(check.failure ?? "")}`);
|
|
266
|
+
return check;
|
|
267
|
+
}
|
|
268
|
+
await new Promise(resolve => setTimeout(resolve, Math.min(1000, Math.max(0, deadline - Date.now()))));
|
|
269
|
+
} while (Date.now() < deadline);
|
|
270
|
+
throw new Error(`Check ${runId} timed out`);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
try {
|
|
274
|
+
await monitor.pause(); // pause future scheduling; the first look continues
|
|
275
|
+
await waitForCheck(firstRunId);
|
|
276
|
+
await monitor.refine({ schedule: initial.schedule as MonitorSchedule }); // our known, validated input roundtrips
|
|
277
|
+
await monitor.refine({ schedule: { kind: "daily", at: "09:00", tz: "Asia/Singapore" } });
|
|
278
|
+
const task = await monitor.runNow(); // one explicit manual check while scheduling is paused
|
|
279
|
+
const check = await waitForCheck(task.runId); // 202 acceptance is not completion
|
|
280
|
+
const checks = await monitor.runs({ limit: 10, offset: 0 });
|
|
281
|
+
await monitor.resume();
|
|
282
|
+
} finally {
|
|
283
|
+
await monitor.delete(); // soft-delete; any in-flight check continues
|
|
284
|
+
}
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Wire API mapping (all supported routes use `/track/tracks`, not `/monitors`):
|
|
288
|
+
|
|
289
|
+
| Method | Contract |
|
|
290
|
+
| --- | --- |
|
|
291
|
+
| createMonitor / getMonitor | POST / GET Track; create uses instructions/title and wire schedule every_seconds or daily_at/tz. |
|
|
292
|
+
| updateMonitor | PATCH title/instructions/schedule/status; raw instructions is supported, high-level refine is stricter. |
|
|
293
|
+
| runNow / deleteMonitor | POST run_now returns track_id/session_id/run_id; DELETE returns empty 204. |
|
|
294
|
+
| listRuns / getRun | GET checks, local pagination/exact lookup within the recent 50. |
|
|
295
|
+
| api.events / api.intervene / MonitorHandle.events / interactionHandle | Deprecated; local QoniUnsupportedError, zero HTTP and zero token exchange. No Track scope authorizes DoAnything actions. |
|
|
296
|
+
|
|
297
|
+
Legacy targetUrls/extractionSchema/triggerDsl/stopConditionDsl/notifyChannel
|
|
298
|
+
(including snake_case) and url are unsupported local errors, never folded into
|
|
299
|
+
instructions. Cron and legacy event/option types remain exported, deprecated.
|
|
300
|
+
Invalid or missing input schedules raise QoniValidationError before HTTP.
|
|
301
|
+
Optional undefined keys keep their original omission behavior; defined
|
|
302
|
+
unsupported values (including null) are still local errors. Interval tz, when
|
|
303
|
+
specified, is an invalid schedule and raises QoniValidationError.
|
|
304
|
+
|
|
305
|
+
`doAnything.api.readArtifacts` and DoAnythingSnapshot/SnapshotImage/SnapshotAction
|
|
306
|
+
are deprecated: no backend snapshot route/source exists. The method raises
|
|
307
|
+
local QoniUnsupportedError (`unsupported.operation`), zero HTTP. Read actual
|
|
308
|
+
files with `qoni.doAnything.artifacts({ token, runId })` / api.listArtifacts and download via
|
|
309
|
+
artifact.content()/api.artifactDownloadUrl. For images, enable
|
|
310
|
+
capture.screenshots and consume screenshot events. A text artifact is not a
|
|
311
|
+
snapshot. Deprecated unsupported negatives never count as positive capability coverage.
|
|
312
|
+
|
|
218
313
|
## Do Anything Interactions (human handoff)
|
|
219
314
|
|
|
220
315
|
When a run needs the user it pauses and emits one `interaction` event per request (`event.data` is a typed `Interaction`; `wait({ onInteraction })` hands you an `InteractionHandle`). Interactions are typed by what the user must do, never by business: `site_login`, `take_control`, `ask_user`, `confirmation`, plus the system `wait`. `switch (handle.interaction.type)` narrows `payload` per branch.
|
|
@@ -344,3 +439,19 @@ For skill or docs changes that teach integration, also validate from a fresh con
|
|
|
344
439
|
5. Run the consumer project's typecheck and test command.
|
|
345
440
|
|
|
346
441
|
Prefer mocked fetch-based tests for normal verification. Use real GenAuth/GUMem/WebAgent services only for explicit smoke tests, and never hard-code real secrets in repository files.
|
|
442
|
+
|
|
443
|
+
## Authing v3 management additions
|
|
444
|
+
|
|
445
|
+
The SDK provides 591 typed JSON management methods in 35 namespaces, aligned with the official Authing Node SDK 4.0.2 (496 JSON methods) plus GenAuth extensions and compatibility names. Read `docs/genauth-management.md` and `docs/genauth-management-api.md` for exact methods, aliases, inputs and deployment-dependent rules. Preserve the Qoni management-token exchange; do not use a delegated runtime token for management calls.
|
|
446
|
+
|
|
447
|
+
- Use `genauth.users.iterate` for paginated user processing (limit 1-50); pass cancellation in the optional second argument.
|
|
448
|
+
- Roles and resources belong to an existing permission namespace. Resource action names use `resourceCode:action`; newer permission deployments require `apiIdentifier` for API resources.
|
|
449
|
+
- `customFields.set` requires `dataType` for both create and update. `customFields.list` defaults the subject to `USER`.
|
|
450
|
+
- HTTP-200 business errors throw typed Qoni errors, including numeric Authing API codes as strings. Do not replay writes after network or business failures.
|
|
451
|
+
- Respect employment rules: resign a user only when departure is intended. The SDK never automatically resigns users to make deletion succeed.
|
|
452
|
+
- Filtered user queries depend on the GenAuth search index. A successful `get` or batch lookup does not prove that a newly created user is searchable. Keep search acceptance failures visible and diagnose the server index separately.
|
|
453
|
+
- Run `pnpm test:e2e:genauth` using an isolated test userpool; cleanup failures fail the suite. Generated contract coverage does not imply live acceptance of every operation.
|
|
454
|
+
|
|
455
|
+
### Agent Mail and Agent Phone (unreleased extension)
|
|
456
|
+
|
|
457
|
+
Use `qoni.genauth.agentMail` and `qoni.genauth.agentPhone`. Read `docs/genauth-communication.md` and `docs/genauth-communication-api.md` before using these APIs. Runtime requires native GenAuth credentials through `genauthCommunicationCredentials`; administrator calls reuse Qoni management exchange. Preserve stable idempotency keys, pass current revisions, and inspect original operations after ambiguous results. Never treat an unknown purchase/send outcome as safe to repeat. Phone numbers, mailboxes, and communication personas are distinct from login email/phone and delegation agentId.
|