@qoniai/qoni 0.7.0 → 0.8.0
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 +25 -2
- package/README.md +30 -2
- package/README.zh-CN.md +27 -2
- package/dist/index.cjs +40 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -3
- package/dist/index.d.ts +14 -3
- package/dist/index.js +40 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.8.0 - Unreleased
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- `qoni.genauth.delegateAgent(input)` returns the grant directly (`grant.token`),
|
|
8
|
+
or an interactive authorization request, through the existing signed endpoint.
|
|
9
|
+
- `agentKey` is an alias for `agent`. Existing root and `qoni.qoni` entries keep
|
|
10
|
+
their `QoniResponse` envelopes. Conflicting Agent labels are rejected;
|
|
11
|
+
the existing top-level `userId` precedence is unchanged.
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- Expiry strings such as `"15m"` are converted to integer seconds; valid
|
|
16
|
+
durations are 60–86400 seconds, matching the GenAuth DTO.
|
|
17
|
+
- Unsupported `deniedScopes` is rejected rather than forwarding an unenforced
|
|
18
|
+
deny policy. Legacy `mailbox.*`, `web.search`, and `web.extract` scopes are rejected without mapping to product scopes.
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- Expiry validation now happens locally before signing. Invalid formats and
|
|
23
|
+
values outside 60–86400 seconds throw `QoniValidationError`; numeric seconds
|
|
24
|
+
strings are sent as integers.
|
|
25
|
+
|
|
3
26
|
## 0.7.0 - 2026-10-02
|
|
4
27
|
|
|
5
28
|
### Changed
|
|
@@ -136,8 +159,8 @@ compatible fixes.
|
|
|
136
159
|
|
|
137
160
|
### Changed
|
|
138
161
|
|
|
139
|
-
- The default Qoni host is now `https://dashboard.qoni.ai
|
|
140
|
-
|
|
162
|
+
- The default Qoni host is now `https://dashboard.qoni.ai`.
|
|
163
|
+
Callers that never passed an explicit `host`
|
|
141
164
|
reach a different control plane after upgrading. Note that the new default has
|
|
142
165
|
no `/dashboard` path segment — pass `host` explicitly for private or local
|
|
143
166
|
deployments.
|
package/README.md
CHANGED
|
@@ -251,7 +251,7 @@ Silent product calls receive `token: delegation.data.token`. If that token is an
|
|
|
251
251
|
|
|
252
252
|
- **Silent** (`mode: "silent"`, the default) — acts for one end user, so a user
|
|
253
253
|
is **required**: pass `user: { id: <genauthUserId> }` (preferred) or the
|
|
254
|
-
|
|
254
|
+
top-level `userId`. Omitting it throws `QoniValidationError`
|
|
255
255
|
locally before any request. Authorize with the `products` sugar and/or
|
|
256
256
|
explicit `scopes`.
|
|
257
257
|
- **Interactive** (`mode: "interactive"`) — pass `redirectUri`, `state`,
|
|
@@ -306,7 +306,35 @@ await qoni.gumem.recall({
|
|
|
306
306
|
});
|
|
307
307
|
```
|
|
308
308
|
|
|
309
|
-
`completeDelegateToken` is the recommended server callback helper. `delegateAgent` and `completeDelegateAgent` remain as deprecated compatibility names, but the current callback contract is `{ grantId, code, state }`; old `{ code, state }` completion is not supported.
|
|
309
|
+
`completeDelegateToken` is the recommended server callback helper. Root `qoni.delegateAgent` and `qoni.completeDelegateAgent` remain as deprecated compatibility names, but the current callback contract is `{ grantId, code, state }`; old `{ code, state }` completion is not supported.
|
|
310
|
+
|
|
311
|
+
## Original delegation style (0.8.0, unreleased)
|
|
312
|
+
|
|
313
|
+
```ts
|
|
314
|
+
const grant = await qoni.genauth.delegateAgent({
|
|
315
|
+
userId: process.env.QONI_USER_ID!, // The current user's GenAuth ID
|
|
316
|
+
agentKey: "email-assistant", // Agent audit label; alias for agent
|
|
317
|
+
scopes: [
|
|
318
|
+
"webagent.do_anything:read", // Read status, events, and artifacts
|
|
319
|
+
"webagent.do_anything:manage", // Start/cancel tasks and respond to interactions
|
|
320
|
+
],
|
|
321
|
+
expiresIn: "15m", // Converted to 900 seconds before signing
|
|
322
|
+
});
|
|
323
|
+
console.log(grant.grantId, grant.auditId);
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
This entry returns the grant directly; root `delegateToken` still returns
|
|
327
|
+
`QoniResponse`, with the token in `response.data.token`. Interactive mode returns
|
|
328
|
+
the authorization request directly; use `completeDelegateToken()` in the server
|
|
329
|
+
callback after consent. `agentKey` aliases `agent`; both labels must agree when
|
|
330
|
+
supplied together. Top-level `userId` retains precedence over `user.id`.
|
|
331
|
+
Expiry accepts integer seconds or whole-number
|
|
332
|
+
`s/m/h/d` durations, between 60 and 86400 seconds. Until published, use the 0.8.0
|
|
333
|
+
source build for these additions.
|
|
334
|
+
|
|
335
|
+
Legacy `mailbox.*`, `web.search`, and `web.extract` scopes, and `deniedScopes`, are unsupported and rejected.
|
|
336
|
+
They are never expanded into broader product permissions. Product scopes do not
|
|
337
|
+
prohibit email sending or deletion; a prompt is not an enforced deny policy.
|
|
310
338
|
|
|
311
339
|
## Choosing Scopes
|
|
312
340
|
|
package/README.zh-CN.md
CHANGED
|
@@ -227,7 +227,7 @@ silent 产品能力方法传入 `token: delegation.data.token`。如果这个 to
|
|
|
227
227
|
|
|
228
228
|
**入参形态。** `delegateToken` 用 `mode` 判别:
|
|
229
229
|
|
|
230
|
-
- **Silent**(`mode: "silent"`,默认)——代表某个终端用户,所以 user **必填**:传 `user: { id: <genauthUserId> }
|
|
230
|
+
- **Silent**(`mode: "silent"`,默认)——代表某个终端用户,所以 user **必填**:传 `user: { id: <genauthUserId> }`(推荐)或顶层 `userId`。不传会在请求发出前本地抛 `QoniValidationError`。授权用 `products` 语法糖和/或显式 `scopes`。
|
|
231
231
|
- **Interactive**(`mode: "interactive"`)——传 `redirectUri`、`state`、`agent`、`scopes`,**不传** user(Qoni Console 在授权时解析登录)。返回 `data.authorizationUrl` 而非 token;在服务端用 `completeDelegateToken({ grantId, code, state })` 完成。
|
|
232
232
|
|
|
233
233
|
`agent` 可选,默认 `"sdk"`。
|
|
@@ -277,7 +277,32 @@ await qoni.gumem.recall({
|
|
|
277
277
|
});
|
|
278
278
|
```
|
|
279
279
|
|
|
280
|
-
`completeDelegateToken` 是推荐的服务端 callback helper
|
|
280
|
+
`completeDelegateToken` 是推荐的服务端 callback helper。顶层 `qoni.delegateAgent` 和 `qoni.completeDelegateAgent` 仍保留为 deprecated 兼容命名,但当前 callback 契约是 `{ grantId, code, state }`;旧的 `{ code, state }` complete 签名不再兼容。
|
|
281
|
+
|
|
282
|
+
## 原始委托写法(0.8.0,尚未发布)
|
|
283
|
+
|
|
284
|
+
```ts
|
|
285
|
+
const grant = await qoni.genauth.delegateAgent({
|
|
286
|
+
userId: process.env.QONI_USER_ID!, // 当前用户的 GenAuth ID
|
|
287
|
+
agentKey: "email-assistant", // Agent 审计标签,兼容 agent 参数
|
|
288
|
+
scopes: [
|
|
289
|
+
"webagent.do_anything:read", // 读取任务状态、事件和产物
|
|
290
|
+
"webagent.do_anything:manage", // 启动、取消任务和提交交互响应
|
|
291
|
+
],
|
|
292
|
+
expiresIn: "15m", // 签名前转换为 900 秒
|
|
293
|
+
});
|
|
294
|
+
console.log(grant.grantId, grant.auditId);
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
此入口直接返回 grant;顶层 `delegateToken` 仍返回 `QoniResponse`,token 在
|
|
298
|
+
`response.data.token` 中。交互式模式直接返回授权请求;用户同意后,在服务端回调
|
|
299
|
+
用 `completeDelegateToken()` 兑换 token。`agentKey`、`userId` 分别兼容 `agent`、
|
|
300
|
+
`user.id`;同时提供两个 Agent 标签时必须一致,顶层 `userId` 保持原有的优先级。
|
|
301
|
+
有效期支持整数秒或整数 `s/m/h/d` 时长,
|
|
302
|
+
范围为 60–86400 秒。这些新增能力在发布前需要使用 0.8.0 源码构建。
|
|
303
|
+
|
|
304
|
+
旧版 `mailbox.*`、`web.search`、`web.extract` scope 和 `deniedScopes` 不受支持,SDK 会拒绝,不会将它们
|
|
305
|
+
扩展成更宽的产品权限。产品 scope 不限制发送、删除邮件;任务描述不能代替禁止策略。
|
|
281
306
|
|
|
282
307
|
## Scope 怎么选
|
|
283
308
|
|
package/dist/index.cjs
CHANGED
|
@@ -1612,7 +1612,14 @@ function createQoniNamespace(_transport, delegateToken, completeDelegateToken) {
|
|
|
1612
1612
|
|
|
1613
1613
|
// src/genauth.ts
|
|
1614
1614
|
function createGenAuthNamespace(transport, options = {}) {
|
|
1615
|
+
async function delegateAgent(input) {
|
|
1616
|
+
if (!options.delegateTokenFor) {
|
|
1617
|
+
throw new QoniValidationError("GenAuth delegateAgent requires a Qoni delegation provider");
|
|
1618
|
+
}
|
|
1619
|
+
return (await options.delegateTokenFor(input)).data;
|
|
1620
|
+
}
|
|
1615
1621
|
return {
|
|
1622
|
+
delegateAgent,
|
|
1616
1623
|
userInfo: (input) => transport.genauthJson("GET", "/oidc/me", input.accessToken),
|
|
1617
1624
|
jwks: () => transport.genauthJson("GET", "/oidc/.well-known/jwks.json"),
|
|
1618
1625
|
discovery: () => transport.genauthJson("GET", "/oidc/.well-known/openid-configuration"),
|
|
@@ -2248,7 +2255,8 @@ var Qoni = class {
|
|
|
2248
2255
|
webAgentSSE: (...args) => this.webAgentSSE(...args)
|
|
2249
2256
|
};
|
|
2250
2257
|
this.genauth = createGenAuthNamespace(transport, {
|
|
2251
|
-
adminTokenFor: () => this.genauthAdminTokenFor()
|
|
2258
|
+
adminTokenFor: () => this.genauthAdminTokenFor(),
|
|
2259
|
+
delegateTokenFor: (input) => this.delegateToken(input)
|
|
2252
2260
|
});
|
|
2253
2261
|
this.qoni = createQoniNamespace(
|
|
2254
2262
|
transport,
|
|
@@ -2961,19 +2969,43 @@ function toRecord(value) {
|
|
|
2961
2969
|
}
|
|
2962
2970
|
function normalizeDelegateTokenInput(input) {
|
|
2963
2971
|
const body = toRecord(input);
|
|
2972
|
+
if (input.deniedScopes !== void 0) {
|
|
2973
|
+
throw new QoniValidationError(
|
|
2974
|
+
"delegateToken: deniedScopes is not supported by the delegation service. Do not rely on this field to prevent mailbox or browser actions."
|
|
2975
|
+
);
|
|
2976
|
+
}
|
|
2977
|
+
if (input.agent !== void 0 && input.agentKey !== void 0 && input.agent !== input.agentKey) {
|
|
2978
|
+
throw new QoniValidationError("delegateToken: agent and agentKey must identify the same Agent");
|
|
2979
|
+
}
|
|
2964
2980
|
if (!body.userId && isJsonObject(input.user) && typeof input.user.id === "string") {
|
|
2965
2981
|
body.userId = input.user.id;
|
|
2966
2982
|
}
|
|
2967
2983
|
if (input.mode !== "interactive" && !body.userId) {
|
|
2968
2984
|
throw new QoniValidationError(
|
|
2969
|
-
'delegateToken: silent mode requires a user \u2014 pass `user: { id: <genauthUserId> }` (or the
|
|
2985
|
+
'delegateToken: silent mode requires a user \u2014 pass `user: { id: <genauthUserId> }` (or the top-level `userId`). Interactive mode (`mode: "interactive"`) does not need one.'
|
|
2970
2986
|
);
|
|
2971
2987
|
}
|
|
2972
|
-
body.agent = input.agent ?? "sdk";
|
|
2988
|
+
body.agent = input.agent ?? input.agentKey ?? "sdk";
|
|
2989
|
+
delete body.agentKey;
|
|
2990
|
+
if (input.expiresIn !== void 0) body.expiresIn = delegationDurationSeconds(input.expiresIn);
|
|
2973
2991
|
body.scopes = resolveDelegationScopes(input.scopes, input.products);
|
|
2974
2992
|
delete body.products;
|
|
2975
2993
|
return body;
|
|
2976
2994
|
}
|
|
2995
|
+
function delegationDurationSeconds(value) {
|
|
2996
|
+
let seconds = value;
|
|
2997
|
+
if (typeof value === "string") {
|
|
2998
|
+
const duration = /^(\d+)(s|m|h|d)?$/.exec(value);
|
|
2999
|
+
const units = { s: 1, m: 60, h: 3600, d: 86400 };
|
|
3000
|
+
seconds = duration ? Number(duration[1]) * units[duration[2] ?? "s"] : NaN;
|
|
3001
|
+
}
|
|
3002
|
+
if (typeof seconds !== "number" || !Number.isInteger(seconds) || seconds < 60 || seconds > 86400) {
|
|
3003
|
+
throw new QoniValidationError(
|
|
3004
|
+
'delegateToken: expiresIn must be 60\u201386400 integer seconds or a whole-number duration such as "15m"'
|
|
3005
|
+
);
|
|
3006
|
+
}
|
|
3007
|
+
return seconds;
|
|
3008
|
+
}
|
|
2977
3009
|
function resolveDelegationScopes(scopes, products) {
|
|
2978
3010
|
const resolved = /* @__PURE__ */ new Set();
|
|
2979
3011
|
for (const product of products ?? []) {
|
|
@@ -2997,6 +3029,11 @@ function resolveDelegationScopes(scopes, products) {
|
|
|
2997
3029
|
return [...resolved];
|
|
2998
3030
|
}
|
|
2999
3031
|
function validateScopeFormat(scope) {
|
|
3032
|
+
if (scope.startsWith("mailbox.") || scope === "web.search" || scope === "web.extract") {
|
|
3033
|
+
throw new QoniValidationError(
|
|
3034
|
+
`delegateToken: legacy action scope "${scope}" has no enforced mapping in this delegation service. It will not be expanded into broader product permissions.`
|
|
3035
|
+
);
|
|
3036
|
+
}
|
|
3000
3037
|
if (KNOWN_SCOPES.includes(scope)) return;
|
|
3001
3038
|
const prefixed = KNOWN_SCOPES.find((known) => known.endsWith(`.${scope}`) || known === `webagent.${scope}`);
|
|
3002
3039
|
if (prefixed) {
|