@agentunion/fastaun-browser 0.4.11 → 0.4.12

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 CHANGED
@@ -6,6 +6,26 @@
6
6
 
7
7
  ---
8
8
 
9
+ ## 0.4.12 — 2026-06-08
10
+
11
+ ### 新功能
12
+ - **应用层事件信封(envelope)**:`message.*` 与 `group.changed` 事件发布给应用层时注入 `envelope` 字段,聚合 `message_id`/`seq`/`from`/`to`/`group_id`/`action` 等元数据;顶层别名字段在兼容期保留,计划于 `0.5.*` 移除,请改用 `envelope.*` 访问(四语言对齐)
13
+ - **撤回事件携带 message_id 与自身信封**:`message.recalled` / `group.message_recalled` 通知补全 `message_id` 字段并继承原消息的信封键(四语言对齐)
14
+
15
+ ### 修复
16
+ - **入群首个事件被旧序号阻塞**:`isSelfJoinGroupChanged` 识别自己入群后,将本地 `group_event` seq 基线对齐到 `eventSeq-1`,避免被入群前不可见事件挡住(四语言对齐)
17
+ - **过期 token 重连死循环**:重连前同步 `_identity` 中的 token 状态到 `_sessionParams`,过期或缺失则清空以触发两阶段重新登录,避免反复用旧 token 触发 4001(四语言对齐)
18
+ - **Service Proxy 持久隧道重连**:新增指数退避(上限 60s,成功后重置);`AuthError` 触发 `_authenticateForAccessToken` 重新登录后再重连(四语言对齐)
19
+ - **protected_headers 参数兼容**:`mergeInstanceProtectedHeaders` 同时识别 `protected_headers` 与 `headers` 别名
20
+
21
+ ### 测试
22
+ - `自己入群首个 event_seq>1 不应被入群前不可见事件阻塞`
23
+ - `pull 缺失中间 event_seq 时视为永久空洞,不阻塞已拿到的群事件发布`
24
+ - `publishAppEvent 为群事件注入 envelope 并保留顶层兼容字段`
25
+ - `撤回事件发布给应用层时带撤回通知自身 envelope`
26
+
27
+ ---
28
+
9
29
  ## 0.4.11 — 2026-06-08
10
30
 
11
31
  ### 新功能
@@ -6,6 +6,26 @@
6
6
 
7
7
  ---
8
8
 
9
+ ## 0.4.12 — 2026-06-08
10
+
11
+ ### 新功能
12
+ - **应用层事件信封(envelope)**:`message.*` 与 `group.changed` 事件发布给应用层时注入 `envelope` 字段,聚合 `message_id`/`seq`/`from`/`to`/`group_id`/`action` 等元数据;顶层别名字段在兼容期保留,计划于 `0.5.*` 移除,请改用 `envelope.*` 访问(四语言对齐)
13
+ - **撤回事件携带 message_id 与自身信封**:`message.recalled` / `group.message_recalled` 通知补全 `message_id` 字段并继承原消息的信封键(四语言对齐)
14
+
15
+ ### 修复
16
+ - **入群首个事件被旧序号阻塞**:`isSelfJoinGroupChanged` 识别自己入群后,将本地 `group_event` seq 基线对齐到 `eventSeq-1`,避免被入群前不可见事件挡住(四语言对齐)
17
+ - **过期 token 重连死循环**:重连前同步 `_identity` 中的 token 状态到 `_sessionParams`,过期或缺失则清空以触发两阶段重新登录,避免反复用旧 token 触发 4001(四语言对齐)
18
+ - **Service Proxy 持久隧道重连**:新增指数退避(上限 60s,成功后重置);`AuthError` 触发 `_authenticateForAccessToken` 重新登录后再重连(四语言对齐)
19
+ - **protected_headers 参数兼容**:`mergeInstanceProtectedHeaders` 同时识别 `protected_headers` 与 `headers` 别名
20
+
21
+ ### 测试
22
+ - `自己入群首个 event_seq>1 不应被入群前不可见事件阻塞`
23
+ - `pull 缺失中间 event_seq 时视为永久空洞,不阻塞已拿到的群事件发布`
24
+ - `publishAppEvent 为群事件注入 envelope 并保留顶层兼容字段`
25
+ - `撤回事件发布给应用层时带撤回通知自身 envelope`
26
+
27
+ ---
28
+
9
29
  ## 0.4.11 — 2026-06-08
10
30
 
11
31
  ### 新功能
@@ -56,14 +56,16 @@ SDK 优先使用 prekey_ecdh_v2,并默认要求前向保密:
56
56
 
57
57
  `protected_headers` 会随 E2EE 信封发送,接收端可以读取,因此它提供完整性保护,不提供机密性保护。不要把访问令牌、私钥、隐私正文或其他只允许端到端可见的内容放入 `protected_headers`;这类内容应放进加密的 `payload`。
58
58
 
59
- 发送方可以在以下 SDK 调用中传入 `protected_headers`,各 SDK 也兼容别名 `headers`:
59
+ 推荐通过 SDK 实例级 setter 设置稳定元数据,例如 `client.set_protected_headers(...)` / `client.setProtectedHeaders(...)` / `client.SetProtectedHeaders(...)`。发送方也可以在以下 SDK 调用中传入顶层 `protected_headers` 作为单次发送的高级覆盖;`headers` 仅作为兼容旧调用的别名,不推荐新代码使用:
60
60
 
61
61
  - `message.send`
62
62
  - `message.thought.put`
63
63
  - `group.send`
64
64
  - `group.thought.put`
65
65
 
66
- `payload_type` 不需要应用层传入。SDK 会读取加密前 `payload.type`,自动写入 `protected_headers.payload_type`,接收端解密后会校验它与明文 `payload.type` 一致。
66
+ `payload_type` 不需要应用层传入。SDK 会读取加密前 `payload.type`,自动写入 `protected_headers.payload_type`,接收端解密后会校验它与明文 `payload.type` 一致。
67
+
68
+ `protected_headers` / `headers` 是 send/thought 参数的顶层字段,不放入单独的 `envelope` 入参对象,也不属于业务 `payload`。裸 WebSocket 客户端若自行发送已加密信封,需要把 protected headers 放在自构造的 E2EE 信封内并自行完成 `_auth`,服务端不会替裸 RPC 调用生成或校验明文侧的 protected headers。
67
69
 
68
70
  示例:
69
71
 
@@ -989,7 +989,7 @@ await client.call("group.set_settings", {
989
989
  | `payload` | object | 否 | 消息内容 |
990
990
  | `type` | string | 否 | 信封/封装类型,普通业务消息无需填写;SDK 加密群消息时自动使用 `e2ee.group_encrypted` |
991
991
  | `attachments` | array | 否 | 兼容旧接口的顶层附件元数据;推荐把业务附件放入 `payload.attachments` |
992
- | `protected_headers` / `headers` | object | 否 | SDK 加密前读取的 E2EE 信封元数据,类似 HTTP headers;服务端不解释,接收端验 `_auth` 后在 `e2ee.protected_headers` 暴露 |
992
+ | `protected_headers` / `headers` | object | 否 | SDK 加密前读取的 E2EE 信封元数据,类似 HTTP headers;推荐使用 `protected_headers`,`headers` 仅作为兼容别名;服务端不解释,接收端验 `_auth` 后在 `e2ee.protected_headers` 暴露 |
993
993
 
994
994
  ### Payload 参考约定
995
995
 
@@ -1049,7 +1049,7 @@ SDK 调用时必须走群组 E2EE。应用层传入明文 `payload`,SDK 会加
1049
1049
  | `encrypt` | boolean | 否 | SDK 侧固定按 `true` 处理;`false` 会被拒绝 |
1050
1050
  | `thought_id` | string | 否 | thought item ID;不传时 SDK 生成 `gt-*` |
1051
1051
  | `timestamp` | integer | 否 | 客户端时间戳;不传时 SDK 生成 |
1052
- | `protected_headers` / `headers` | object | 否 | SDK 加密前读取的 E2EE 信封元数据;`context` 会被 SDK 复制进信封并单独验 `_auth` |
1052
+ | `protected_headers` / `headers` | object | 否 | SDK 加密前读取的 E2EE 信封元数据;推荐使用 `protected_headers`,`headers` 仅作为兼容别名;`context` 会被 SDK 复制进信封并单独验 `_auth` |
1053
1053
 
1054
1054
  **SDK 调用示例**:
1055
1055
 
@@ -1957,20 +1957,29 @@ CAS 轮换群组 E2EE Epoch。需要 **admin 及以上**权限。
1957
1957
 
1958
1958
  **Payload**:
1959
1959
 
1960
- ```json
1961
- {
1962
- "module_id": "group",
1963
- "action": "member_added",
1964
- "group_id": "g-abc123.agentid.pub",
1960
+ ```json
1961
+ {
1962
+ "envelope": {
1963
+ "module_id": "group",
1964
+ "action": "member_added",
1965
+ "group_id": "g-abc123.agentid.pub",
1966
+ "event_seq": 42
1967
+ },
1968
+ "module_id": "group",
1969
+ "action": "member_added",
1970
+ "group_id": "g-abc123.agentid.pub",
1965
1971
  "event_seq": 42
1966
- }
1967
- ```
1968
-
1969
- | 字段 | 类型 | 说明 |
1970
- |------|------|------|
1971
- | `module_id` | string | 固定 `"group"` |
1972
- | `action` | string | 变更类型(见下表) |
1973
- | `group_id` | string | 群组 ID |
1972
+ }
1973
+ ```
1974
+
1975
+ SDK 交付给应用层的群事件信封字段统一放在 `envelope`。0.4.x 兼容期仍保留顶层 `module_id` / `action` / `group_id` / `event_seq` 等别名,下一个大版本 0.5.* 将移除这些顶层别名,请通过 `ev["envelope"]["action"]` 等路径访问。
1976
+
1977
+ | 字段 | 类型 | 说明 |
1978
+ |------|------|------|
1979
+ | `envelope` | object | 群事件信封,包含 `module_id`、`action`、`group_id`、`event_seq`、`event_type`、`actor_aid`、`created_at`、`device_id`、`slot_id` 等存在的字段 |
1980
+ | `module_id` | string | 固定 `"group"` |
1981
+ | `action` | string | 变更类型(见下表) |
1982
+ | `group_id` | string | 群组 ID |
1974
1983
  | `event_seq` | integer | 可选,服务端分配的单调递增序号,用于 SDK 内部保序去重 |
1975
1984
  | `request_id` | string | 可选,仅资源审批相关 action |
1976
1985
  | `resource_path` | string | 可选,仅资源相关 action |
@@ -2065,37 +2074,73 @@ SDK 收到后自动解密 `payload`,解密后的明文消息直接交付用户
2065
2074
  }
2066
2075
  ```
2067
2076
 
2068
- SDK 收到后自动调用 `group.pull` 拉取最新消息并逐条解密后交付用户回调。
2069
-
2070
- ### event/group.message_recalled
2077
+ SDK 收到后自动调用 `group.pull` 拉取最新消息并逐条解密后交付用户回调。
2078
+
2079
+ **SDK 应用层回调形态**:
2080
+
2081
+ ```json
2082
+ {
2083
+ "envelope": {
2084
+ "group_id": "g-abc123.agentid.pub",
2085
+ "seq": 42,
2086
+ "message_id": "uuid",
2087
+ "sender_aid": "alice.agentid.pub",
2088
+ "message_type": "group.message",
2089
+ "dispatch_mode": "broadcast"
2090
+ },
2091
+ "group_id": "g-abc123.agentid.pub",
2092
+ "seq": 42,
2093
+ "message_id": "uuid",
2094
+ "sender_aid": "alice.agentid.pub",
2095
+ "message_type": "group.message",
2096
+ "dispatch_mode": "broadcast",
2097
+ "payload": {"type": "text", "text": "Hello"}
2098
+ }
2099
+ ```
2100
+
2101
+ SDK 交付给应用层的 `payload` 是明文业务 JSON 对象;群消息信封字段统一放在 `envelope`。0.4.x 兼容期仍保留顶层 `group_id` / `seq` / `message_id` / `sender_aid` 等别名,下一个大版本 0.5.* 将移除这些顶层别名,请通过 `msg["envelope"]["message_id"]` 等路径访问。
2102
+
2103
+ ### event/group.message_recalled
2071
2104
 
2072
2105
  群消息撤回后推送给所有在线成员(与 pull 双 tombstone 兜底互补)。在线 push 是实时通道,双 tombstone 是离线 / 未读 / push 丢失时的可靠性兜底;两者最终一致,SDK 去重保证应用层只感知一次。
2073
2106
 
2074
2107
  **Payload**:
2075
2108
 
2076
- ```json
2077
- {
2078
- "module_id": "group",
2079
- "group_id": "g-abc123.agentid.pub",
2080
- "seq": 43,
2081
- "message_id": "grm-uuid",
2082
- "message_ids": ["gm-aaa"],
2083
- "target_message_seqs": [42],
2084
- "sender_aid": "alice.agentid.pub",
2109
+ ```json
2110
+ {
2111
+ "envelope": {
2112
+ "module_id": "group",
2113
+ "group_id": "g-abc123.agentid.pub",
2114
+ "seq": 43,
2115
+ "message_id": "grm-uuid",
2116
+ "sender_aid": "alice.agentid.pub"
2117
+ },
2118
+ "module_id": "group",
2119
+ "group_id": "g-abc123.agentid.pub",
2120
+ "seq": 43,
2121
+ "message_id": "grm-uuid",
2122
+ "tombstone_message_id": "grm-uuid",
2123
+ "message_ids": ["gm-aaa"],
2124
+ "target_message_seqs": [42],
2125
+ "sender_aid": "alice.agentid.pub",
2085
2126
  "recalled_by": "alice.agentid.pub",
2086
2127
  "recalled_at": 1234567890000,
2087
2128
  "reason": "",
2088
2129
  "member_aids": ["bob.agentid.pub"]
2089
- }
2090
- ```
2091
-
2092
- | 字段 | 类型 | 说明 |
2093
- |------|------|------|
2094
- | `module_id` | string | 固定 `"group"` |
2095
- | `group_id` | string | 群组 ID |
2096
- | `seq` | integer | 撤回通知 tombstone 的**新群消息 seq** |
2097
- | `message_id` | string | 撤回通知 tombstone 自己的 message_id |
2098
- | `message_ids` | string[] | 被撤回的**原消息 ID 列表** |
2130
+ }
2131
+ ```
2132
+
2133
+ SDK 交付给应用层的撤回事件同样带 `envelope`。`envelope` 表示当前交付的撤回 tombstone / 通知自身信封,不是被撤回原消息的信封;业务侧被撤回的原消息列表继续使用 `message_ids` / `target_message_seqs`。0.4.x 兼容期仍保留顶层 `group_id` / `seq` / `message_id` / `sender_aid` 等别名,下一个大版本 0.5.* 将移除这些顶层别名。
2134
+
2135
+ | 字段 | 类型 | 说明 |
2136
+ |------|------|------|
2137
+ | `envelope` | object | 撤回 tombstone / 通知自身信封,包含 `module_id`、`group_id`、`seq`、`message_id`、`sender_aid`、`device_id`、`slot_id` 等存在的字段 |
2138
+ | `module_id` | string | 固定 `"group"` |
2139
+ | `group_id` | string | 群组 ID |
2140
+ | `seq` | integer | 当前交付的撤回 tombstone / 通知 seq;在线 push 为 notice_seq,原 seq 占位 tombstone 为原消息 seq |
2141
+ | `message_id` | string | 当前交付的撤回 tombstone / 通知自己的 message_id |
2142
+ | `tombstone_message_id` | string | 兼容别名,等同于撤回 tombstone / 通知自身的 `message_id` |
2143
+ | `message_ids` | string[] | 被撤回的**原消息 ID 列表** |
2099
2144
  | `target_message_seqs` | integer[] | 被撤回的原消息 seq 列表 |
2100
2145
  | `sender_aid` | string | 原消息发送方 |
2101
2146
  | `recalled_by` | string | 撤回操作者 |
@@ -78,7 +78,7 @@ P2P `message.*` 的最终投递语义由连接阶段声明的 `delivery_mode`
78
78
  | `encrypted` | boolean | 否 | `false` | 底层 RPC 的 E2EE 标记。Python SDK 便捷层通常使用 `encrypt` 入参并由 SDK 自动填充此字段 |
79
79
  | `message_id` | string | 否 | — | 幂等键(客户端提供或服务端生成 UUID) |
80
80
  | `timestamp` | integer | 否 | — | 客户端时间戳(毫秒)。**服务端忽略此字段,始终使用服务端时间** |
81
- | `protected_headers` / `headers` | object | 否 | — | SDK 加密前读取的 E2EE 信封元数据,类似 HTTP headers;服务端不解释,接收端验 `_auth` 后在 `e2ee.protected_headers` 暴露 |
81
+ | `protected_headers` / `headers` | object | 否 | — | SDK 加密前读取的 E2EE 信封元数据,类似 HTTP headers;推荐使用 `protected_headers`,`headers` 仅作为兼容别名;服务端不解释,接收端验 `_auth` 后在 `e2ee.protected_headers` 暴露 |
82
82
 
83
83
  > 连接级 `delivery_mode` 在 `auth.connect` 阶段声明,结构见 `02-WebSocket协议.md`。Python SDK 的 P2P 消息发送会沿用当前连接的 `delivery_mode`,应用层发送时无需重复指定。
84
84
  > `protected_headers` 只在 SDK 加密路径生效;裸 RPC 发送明文或已加密信封时,调用方需自行遵守 [05-E2EE加密通信](05-E2EE加密通信.md#protectedheaders-与可验证上下文) 的格式和校验规则。
@@ -156,7 +156,7 @@ SDK 调用时必须走 P2P E2EE。应用层传入明文 `payload`,SDK 会加
156
156
  | `encrypt` | boolean | 否 | SDK 侧固定按 `true` 处理;`false` 会被拒绝 |
157
157
  | `thought_id` | string | 否 | thought item ID;不传时 SDK 生成 `mt-*` |
158
158
  | `timestamp` | integer | 否 | 客户端时间戳;不传时 SDK 生成 |
159
- | `protected_headers` / `headers` | object | 否 | SDK 加密前读取的 E2EE 信封元数据;`context` 会被 SDK 复制进信封并单独验 `_auth` |
159
+ | `protected_headers` / `headers` | object | 否 | SDK 加密前读取的 E2EE 信封元数据;推荐使用 `protected_headers`,`headers` 仅作为兼容别名;`context` 会被 SDK 复制进信封并单独验 `_auth` |
160
160
 
161
161
  ### SDK 调用示例
162
162
 
@@ -510,20 +510,30 @@ result = await client.call("message.ack", {"seq": 150})
510
510
 
511
511
  ### Payload
512
512
 
513
- ```json
514
- {
515
- "message_id": "uuid-1",
516
- "from": "alice.agentid.pub",
517
- "to": "bob.agentid.pub",
513
+ ```json
514
+ {
515
+ "envelope": {
516
+ "message_id": "uuid-1",
517
+ "from": "alice.agentid.pub",
518
+ "to": "bob.agentid.pub",
519
+ "seq": 42,
520
+ "timestamp": 1234567890000,
521
+ "encrypted": false
522
+ },
523
+ "message_id": "uuid-1",
524
+ "from": "alice.agentid.pub",
525
+ "to": "bob.agentid.pub",
518
526
  "seq": 42,
519
527
  "timestamp": 1234567890000,
520
528
  "payload": {"type": "text", "text": "Hello!"},
521
529
  "delivery_mode": "queue",
522
530
  "encrypted": false
523
- }
524
- ```
525
-
526
- ### 订阅
531
+ }
532
+ ```
533
+
534
+ SDK 交付给应用层的 `payload` 是明文业务 JSON 对象;信封字段统一放在 `envelope`。0.4.x 兼容期仍保留顶层 `message_id` / `from` / `to` / `seq` / `timestamp` 等别名,下一个大版本 0.5.* 将移除这些顶层别名,请通过 `msg["envelope"]["seq"]` 等路径访问。
535
+
536
+ ### 订阅
527
537
 
528
538
  ```python
529
539
  client.on("message.received", lambda msg: print(msg["payload"]))
@@ -537,21 +547,35 @@ client.on("message.received", lambda msg: print(msg["payload"]))
537
547
 
538
548
  ### Payload
539
549
 
540
- ```json
541
- {
542
- "from": "alice.agentid.pub",
543
- "to": "bob.agentid.pub",
544
- "message_ids": ["uuid-1", "uuid-2"],
545
- "timestamp": 1234567890000
546
- }
547
- ```
548
-
549
- | 字段 | 类型 | 说明 |
550
- |------|------|------|
551
- | `from` | string | 发送方(撤回者)AID |
552
- | `to` | string | 接收方 AID |
553
- | `message_ids` | array | 被撤回的消息 ID 列表 |
554
- | `timestamp` | integer | 服务端时间戳(毫秒) |
550
+ ```json
551
+ {
552
+ "envelope": {
553
+ "message_id": "recall-uuid",
554
+ "from": "alice.agentid.pub",
555
+ "to": "bob.agentid.pub",
556
+ "seq": 43,
557
+ "timestamp": 1234567890000
558
+ },
559
+ "message_id": "recall-uuid",
560
+ "tombstone_message_id": "recall-uuid",
561
+ "from": "alice.agentid.pub",
562
+ "to": "bob.agentid.pub",
563
+ "message_ids": ["uuid-1", "uuid-2"],
564
+ "timestamp": 1234567890000
565
+ }
566
+ ```
567
+
568
+ SDK 交付给应用层的撤回事件同样带 `envelope`。`envelope` 表示撤回 tombstone / 通知自身的信封,不是被撤回原消息的信封;被撤回的原消息继续通过 `message_ids` 表达。0.4.x 兼容期仍保留顶层 `message_id` / `from` / `to` / `seq` / `timestamp` 等别名,下一个大版本 0.5.* 将移除这些顶层别名。
569
+
570
+ | 字段 | 类型 | 说明 |
571
+ |------|------|------|
572
+ | `envelope` | object | 撤回 tombstone / 通知自身信封,包含 `message_id`、`from`、`to`、`seq`、`timestamp`、`device_id`、`slot_id` 等存在的字段 |
573
+ | `message_id` | string | 撤回 tombstone / 通知自身的 message_id |
574
+ | `tombstone_message_id` | string | 兼容别名,等同于撤回 tombstone / 通知自身的 `message_id` |
575
+ | `from` | string | 发送方(撤回者)AID |
576
+ | `to` | string | 接收方 AID |
577
+ | `message_ids` | array | 被撤回的消息 ID 列表 |
578
+ | `timestamp` | integer | 服务端时间戳(毫秒) |
555
579
 
556
580
  ### 订阅
557
581
 
@@ -40,7 +40,7 @@
40
40
  | `to` | `message.send.params` | P2P 接收方 AID |
41
41
  | `group_id` | `group.send.params` 和群消息信封 | 群组 ID |
42
42
  | `context.type + context.id` | `message.thought.put/get.params` 和 `group.thought.put/get.params` | 思考内容 selector;必填,不要只放在 payload 内 |
43
- | `protected_headers` / `headers` | `message.send` / `message.thought.put` / `group.send` / `group.thought.put` 参数 | E2EE 信封元数据,类似 HTTP headersSDK 验 `_auth` 后在 `e2ee.protected_headers` 暴露 |
43
+ | `protected_headers` / `headers` | `message.send` / `message.thought.put` / `group.send` / `group.thought.put` 参数 | E2EE 信封元数据,类似 HTTP headers;推荐 `protected_headers`,`headers` 仅为兼容别名;SDK 验 `_auth` 后在 `e2ee.protected_headers` 暴露 |
44
44
  | `from` / `sender_aid` | 服务端生成的消息信封 | 发送方身份 |
45
45
  | `message_id` / `seq` / `timestamp` / `created_at` | 服务端生成或发送参数 | 当前消息 ID、序号和服务端时间 |
46
46
  | `encrypted` / `delivery_mode` | 发送参数或连接上下文 | 加密和 P2P 投递语义 |
@@ -534,7 +534,7 @@ for obj in result["items"]:
534
534
  | `content_type` | string | 否 | MIME 类型,默认 `"application/octet-stream"` |
535
535
  | `is_private` | boolean | 否 | 是否私有,默认 `true` |
536
536
  | `size_bytes` | integer | 否 | 预期文件大小(用于校验) |
537
- | `skip_blob` | boolean | 否 | 秒传模式,默认 `false`;为 `true` 时跳过 blob 上传,必须提供 `sha256` 且服务端已存在对应内容 |
537
+ | `skip_blob` | boolean | 否 | 秒传模式,默认 `false`;为 `true` 时跳过 blob 上传,必须提供 `sha256`,且当前 owner 已拥有相同内容 |
538
538
  | `expected_version` | integer | 否 | 乐观并发控制版本号 |
539
539
  | `expire_in_seconds` | integer | 否 | 过期时间(秒) |
540
540
  | `metadata` | object | 否 | 自定义元数据 |
@@ -650,22 +650,25 @@ print(f"配额: {limits['quota_used_bytes']}/{limits['quota_total_bytes']}")
650
650
 
651
651
  ## storage.check_upload
652
652
 
653
- 上传预检:一次调用同时回答"文件是否超限"和"是否可秒传"。客户端应在计算完文件 SHA-256 后、实际上传前调用。
653
+ 上传预检:一次调用同时回答"文件是否超限"和"当前 owner 是否可秒传"。客户端应在计算完文件 SHA-256 后、实际上传前调用。
654
+
655
+ > `check_upload` / `complete_upload(skip_blob=true)` 只允许复用当前 owner 已拥有的内容,避免跨 owner 暴露全局 CAS 存在性或跳过上传克隆他人私有内容。不同 owner 上传相同内容时,仍会在完成上传后归一到同一个 CAS blob,由服务端引用计数管理物理去重。
654
656
 
655
657
  ### 参数
656
658
 
657
- | 参数 | 类型 | 必填 | 说明 |
658
- |------|------|------|------|
659
- | `sha256` | string | 是 | 文件内容的 SHA-256 hex(64 字符) |
660
- | `size_bytes` | integer | 是 | 文件大小(字节) |
659
+ | 参数 | 类型 | 必填 | 说明 |
660
+ |------|------|------|------|
661
+ | `sha256` | string | 是 | 文件内容的 SHA-256 hex(64 字符) |
662
+ | `size_bytes` | integer | 是 | 文件大小(字节) |
663
+ | `owner_aid` | string | 否 | 检查指定 owner 是否可秒传,默认当前用户;必须等于当前登录 AID |
661
664
 
662
665
  ### 响应
663
666
 
664
667
  | 字段 | 类型 | 说明 |
665
668
  |------|------|------|
666
669
  | `within_limit` | boolean | 文件大小是否在限制内 |
667
- | `exists` | boolean | 服务端是否已有相同内容 |
668
- | `skip_upload` | boolean | 是否可跳过上传(秒传) |
670
+ | `exists` | boolean | 当前 owner 是否已拥有相同内容且 CAS blob 可用 |
671
+ | `skip_upload` | boolean | 是否可跳过上传(秒传) |
669
672
 
670
673
  ### 使用场景
671
674
 
@@ -683,7 +686,7 @@ check = await client.call("storage.check_upload", {
683
686
  if not check["within_limit"]:
684
687
  print("文件超限,无法上传")
685
688
  elif check["skip_upload"]:
686
- # 秒传:服务端已有相同内容,跳过上传直接 complete
689
+ # 秒传:当前 owner 已拥有相同内容,跳过上传直接 complete
687
690
  await client.call("storage.complete_upload", {
688
691
  "object_key": "my/file.bin",
689
692
  "sha256": sha256,