@agentunion/fastaun-browser 0.3.5 → 0.4.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.
Files changed (74) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/_packed_docs/AUN_SDK_/351/207/215/346/236/204/345/256/236/346/226/275/350/256/241/345/210/222.md +596 -0
  3. package/_packed_docs/AUN_SDK_/351/207/215/346/236/204/350/256/276/350/256/241/346/226/271/346/241/210_v3.md +1633 -0
  4. package/_packed_docs/CHANGELOG.md +14 -0
  5. package/_packed_docs/INDEX.md +17 -11
  6. package/_packed_docs/KITE_DOCS_GUIDE.md +11 -10
  7. package/_packed_docs/sdk/01-/345/277/253/351/200/237/345/274/200/345/247/213.md +134 -158
  8. package/_packed_docs/sdk/02-WebSocket/345/215/217/350/256/256.md +11 -7
  9. package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +98 -119
  10. package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +147 -374
  11. package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +153 -153
  12. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +163 -1364
  13. package/_packed_docs/sdk/07-/351/224/231/350/257/257/345/244/204/347/220/206.md +71 -91
  14. package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +76 -63
  15. package/_packed_docs/sdk/09-custody-api-manual.md +7 -6
  16. package/_packed_docs/sdk/09-meta-rpc-manual.md +13 -14
  17. package/_packed_docs/sdk/09-storage-rpc-manual.md +89 -0
  18. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +37 -49
  19. package/_packed_docs/sdk/INDEX.md +72 -98
  20. package/_packed_docs/sdk/README.md +85 -266
  21. package/dist/aid-store.d.ts +64 -0
  22. package/dist/aid-store.d.ts.map +1 -0
  23. package/dist/aid-store.js +855 -0
  24. package/dist/aid-store.js.map +1 -0
  25. package/dist/aid.d.ts +50 -0
  26. package/dist/aid.d.ts.map +1 -0
  27. package/dist/aid.js +106 -0
  28. package/dist/aid.js.map +1 -0
  29. package/dist/auth.d.ts +17 -1
  30. package/dist/auth.d.ts.map +1 -1
  31. package/dist/auth.js +27 -4
  32. package/dist/auth.js.map +1 -1
  33. package/dist/bundle.js +1981 -2048
  34. package/dist/cert-utils.d.ts +26 -0
  35. package/dist/cert-utils.d.ts.map +1 -0
  36. package/dist/cert-utils.js +221 -0
  37. package/dist/cert-utils.js.map +1 -0
  38. package/dist/client.d.ts +93 -58
  39. package/dist/client.d.ts.map +1 -1
  40. package/dist/client.js +775 -170
  41. package/dist/client.js.map +1 -1
  42. package/dist/error-codes.d.ts +25 -0
  43. package/dist/error-codes.d.ts.map +1 -0
  44. package/dist/error-codes.js +31 -0
  45. package/dist/error-codes.js.map +1 -0
  46. package/dist/errors.d.ts +4 -0
  47. package/dist/errors.d.ts.map +1 -1
  48. package/dist/errors.js +4 -0
  49. package/dist/errors.js.map +1 -1
  50. package/dist/index.d.ts +6 -6
  51. package/dist/index.d.ts.map +1 -1
  52. package/dist/index.js +5 -5
  53. package/dist/index.js.map +1 -1
  54. package/dist/keystore/index.d.ts +1 -1
  55. package/dist/keystore/index.d.ts.map +1 -1
  56. package/dist/result.d.ts +19 -0
  57. package/dist/result.d.ts.map +1 -0
  58. package/dist/result.js +10 -0
  59. package/dist/result.js.map +1 -0
  60. package/dist/transport.d.ts +3 -0
  61. package/dist/transport.d.ts.map +1 -1
  62. package/dist/transport.js +17 -2
  63. package/dist/transport.js.map +1 -1
  64. package/dist/types.d.ts +13 -2
  65. package/dist/types.d.ts.map +1 -1
  66. package/dist/types.js +22 -0
  67. package/dist/types.js.map +1 -1
  68. package/dist/v2/e2ee/encrypt-p2p.js +1 -1
  69. package/dist/v2/e2ee/encrypt-p2p.js.map +1 -1
  70. package/dist/version.d.ts +2 -0
  71. package/dist/version.d.ts.map +1 -0
  72. package/dist/version.js +5 -0
  73. package/dist/version.js.map +1 -0
  74. package/package.json +1 -1
@@ -1,52 +1,50 @@
1
- # AUN SDK Python - 错误处理
1
+ # AUN SDK - 错误处理
2
2
 
3
3
  ---
4
4
 
5
5
  ## 错误类层级
6
6
 
7
- ```
7
+ ```text
8
8
  AUNError
9
- ├── ConnectionError # 网络连接失败
10
- ├── TimeoutError # 操作超时
11
- ├── AuthError # 认证失败(code: 4001/4010/-32001/-32003)
12
- │ ├── CertificateRevokedError # 证书已吊销(code: -32050)
13
- │ └── IdentityConflictError # 身份冲突(code: 4090)
14
- ├── PermissionError # 权限不足(code: 4030/403/-32004)
15
- ├── ValidationError # 参数校验失败(code: 4000/-32600/-32601/-32602)
16
- ├── NotFoundError # 资源不存在(code: 4040/404/-32008)
17
- ├── RateLimitError # 请求限流(code: 4290/429/-32029),retryable=True
18
- ├── VersionConflictError # 版本冲突(code: -32009)
19
- ├── StateError # 非法状态操作
20
- ├── SerializationError # JSON 序列化失败
21
- ├── SessionError # 会话错误(code: -32010/-32011/-32013)
22
- ├── ClientSignatureError # 客户端签名验证失败(code: -32051)
9
+ ├── ConnectionError
10
+ ├── TimeoutError
11
+ ├── AuthError
12
+ │ ├── CertificateRevokedError
13
+ │ └── IdentityConflictError
14
+ ├── PermissionError
15
+ ├── ValidationError
16
+ ├── NotFoundError
17
+ ├── RateLimitError
18
+ ├── VersionConflictError
19
+ ├── StateError
20
+ ├── SerializationError
21
+ ├── SessionError
22
+ ├── ClientSignatureError
23
23
  ├── GroupError
24
- │ ├── GroupNotFoundError # code: -33001
25
- │ └── GroupStateError # code: -33002/-33003
26
24
  └── E2EEError
27
- ├── E2EEDecryptFailedError # P2P 消息解密失败
28
- ├── E2EEDegradedError # E2EE 降级为 long_term_key(无 prekey 可用)
29
- ├── E2EEGroupSecretMissingError # code: -32040,缺少群密钥
30
- ├── E2EEGroupEpochMismatchError # code: -32041,epoch 不匹配
31
- ├── E2EEGroupCommitmentInvalidError # code: -32042,成员承诺验证失败
32
- ├── E2EEGroupNotMemberError # code: -32043,请求者非群成员
33
- └── E2EEGroupDecryptFailedError # code: -32044,群消息解密失败
34
25
  ```
35
26
 
36
- ## 错误属性
27
+ `AIDStore` / `AID` 的可失败方法优先返回 Result 字典;`AUNClient.connect()` / `call()` 等会话方法保留抛异常风格。
28
+
29
+ Result 格式:
37
30
 
38
31
  ```python
39
- from aun_core import AUNError
32
+ loaded = store.load("alice.agentid.pub")
33
+ if not loaded["ok"]:
34
+ print(loaded["error"]["code"], loaded["error"]["message"])
35
+ ```
36
+
37
+ 异常属性:
40
38
 
39
+ ```python
41
40
  try:
42
- await client.call("method.name", {...})
41
+ await client.call("message.send", params)
43
42
  except AUNError as e:
44
- e.code # int,错误码
45
- e.data # Any,附加数据
46
- e.retryable # bool,是否可重试
47
- e.trace_id # str | None,追踪 ID
43
+ print(e.code, e.data, e.retryable, e.trace_id)
48
44
  ```
49
45
 
46
+ ---
47
+
50
48
  ## 错误码速查
51
49
 
52
50
  | 范围 | 含义 | 对应异常 |
@@ -56,24 +54,20 @@ except AUNError as e:
56
54
  | 4030 / 403 | 权限不足 | `PermissionError` |
57
55
  | 4040 / 404 | 资源不存在 | `NotFoundError` |
58
56
  | 4290 / 429 | 请求限流 | `RateLimitError` |
59
- | -32001 / -32003 | 认证失败(协议级) | `AuthError` |
60
- | -32004 | 权限不足(协议级) | `PermissionError` |
61
- | -32008 | 资源不存在(协议级) | `NotFoundError` |
57
+ | -32001 / -32003 | 认证失败 | `AuthError` |
58
+ | -32004 | 权限不足 | `PermissionError` |
59
+ | -32008 | 资源不存在 | `NotFoundError` |
62
60
  | -32009 | 版本冲突 | `VersionConflictError` |
63
61
  | -32010 / -32011 / -32013 | 会话错误 | `SessionError` |
64
- | -32029 | 请求限流(协议级) | `RateLimitError` |
62
+ | -32029 | 请求限流 | `RateLimitError` |
65
63
  | -32600 / -32601 / -32602 | JSON-RPC 参数错误 | `ValidationError` |
66
- | -32040 | 缺少群密钥 | `E2EEGroupSecretMissingError` |
67
- | -32041 | 群 epoch 不匹配 | `E2EEGroupEpochMismatchError` |
68
- | -32042 | 成员承诺验证失败 | `E2EEGroupCommitmentInvalidError` |
69
- | -32043 | 密钥请求者非群成员 | `E2EEGroupNotMemberError` |
70
- | -32044 | 群消息解密失败 | `E2EEGroupDecryptFailedError` |
64
+ | -32040 ~ -32044 | E2EE 群组错误 | `E2EEError` 子类 |
71
65
  | 4090 | 身份冲突 | `IdentityConflictError` |
72
66
  | -32050 | 证书已吊销 | `CertificateRevokedError` |
73
67
  | -32051 | 客户端签名验证失败 | `ClientSignatureError` |
74
- | -33001 | 群组不存在 | `GroupNotFoundError` |
75
- | -33002 / -33003 | 群组状态异常 | `GroupStateError` |
76
- | -33004 ~ -33009 | 群组通用错误 | `GroupError` |
68
+ | -33001 ~ -33009 | 群组错误 | `GroupError` 子类 |
69
+
70
+ ---
77
71
 
78
72
  ## 重试策略
79
73
 
@@ -87,82 +81,68 @@ async def send_with_retry(client, params, max_retries=3):
87
81
  except RateLimitError:
88
82
  await asyncio.sleep(2 ** i)
89
83
  except AuthError:
90
- raise # 认证错误不重试
84
+ raise
91
85
  except AUNError as e:
92
86
  if not e.retryable or i == max_retries - 1:
93
87
  raise
94
88
  await asyncio.sleep(0.5)
95
89
  ```
96
90
 
91
+ ---
92
+
97
93
  ## 常见错误场景
98
94
 
99
- ### 未认证就发消息
95
+ ### 未加载身份
100
96
 
101
97
  ```python
102
- # ❌ 错误:跳过认证直接操作
103
98
  client = AUNClient()
104
- await client.call("message.send", {...}) # AuthError
105
-
106
- # ✅ 正确:先认证再操作
107
- auth = await client.auth.authenticate({"aid": MY_AID})
108
- await client.connect(auth, {})
109
- await client.call("message.send", {...})
99
+ await client.connect() # StateError: no_identity
110
100
  ```
111
101
 
112
- ### E2EE 解密失败
102
+ 修复方式:
113
103
 
114
104
  ```python
115
- # E2EEDecryptFailedError — prekey 不匹配、AAD 篡改、密文损坏
116
- # SDK 自动处理:解密失败的消息返回原始密文,不中断其他消息
105
+ loaded = store.load(MY_AID)
106
+ client.load_identity(loaded["data"]["aid"])
107
+ await client.connect({"auto_reconnect": True})
117
108
  ```
118
109
 
119
- ### 群组 E2EE 错误
110
+ ### 传入字符串 AID 构造客户端
120
111
 
121
112
  ```python
122
- # E2EEGroupSecretMissingError 发送加密群消息时本地无群密钥
123
- # 常见于建群后 create_epoch 尚未完成、或通过邀请码入群后尚未恢复密钥
124
- # SDK 会抛出此异常,调用方可捕获后等待密钥恢复完成再重试
125
- # SDK 自动编排:建群后自动 create_epoch;缺密钥时自动发起恢复请求
126
-
127
- # E2EEGroupDecryptFailedError — 群消息解密失败(密钥不匹配或密文损坏)
128
- # 协议层定义的错误语义,当前 Python SDK 的部分路径表现为返回 None、拒绝状态或跳过自动解密,不一定抛出对应异常
129
-
130
- # E2EEGroupEpochMismatchError — 收到的密钥分发 epoch 低于当前 epoch
131
- # 协议层定义的错误语义,当前 Python SDK 的部分路径表现为返回 None、拒绝状态或跳过自动解密,不一定抛出对应异常
113
+ AUNClient("alice.agentid.pub") # TypeError / ValidationError
114
+ ```
132
115
 
133
- # E2EEGroupCommitmentInvalidError — 收到的密钥分发中成员承诺校验失败
134
- # 协议层定义的错误语义,当前 Python SDK 的部分路径表现为返回 None、拒绝状态或跳过自动解密,不一定抛出对应异常
135
- # 可能原因:中间人篡改、成员列表不一致
116
+ AID 必须来自 `AIDStore.load()`:
136
117
 
137
- # E2EEGroupNotMemberError — 密钥恢复请求被拒,请求者不在群成员列表中
138
- # 协议层定义的错误语义,当前 Python SDK 的部分路径表现为返回 None、拒绝状态或跳过自动解密,不一定抛出对应异常
118
+ ```python
119
+ me = store.load("alice.agentid.pub")["data"]["aid"]
120
+ client = AUNClient(me)
139
121
  ```
140
122
 
141
- ### 身份冲突(v0.3.4+)
123
+ ### 身份冲突
142
124
 
143
125
  ```python
144
- from aun_core import IdentityConflictError
145
-
146
- try:
147
- await client.auth.register_aid({"aid": "alice.agentid.pub"})
148
- except IdentityConflictError as e:
149
- # e.code == 4090
150
- # 场景 1:AID 已被他人注册(公钥不匹配)→ 换一个 AID
151
- # 场景 2:本地有身份但服务端无记录 → 清理本地目录后重试
152
- print(f"身份冲突: {e}")
126
+ registered = await store.register("alice.agentid.pub")
127
+ if not registered["ok"] and registered["error"]["code"] == "IDENTITY_CONFLICT":
128
+ print("AID 已被其他密钥注册,换名或恢复原私钥")
153
129
  ```
154
130
 
155
- 跨语言类名统一为 `IdentityConflictError`(Python / Go / TS / JS)。
131
+ 不要通过删除本地 `AIDs/` 目录解决冲突;私钥丢失后无法证明原 AID 所有权。
156
132
 
157
133
  ### 连接状态错误
158
134
 
159
135
  ```python
160
- # 错误:断连后直接调用
161
- await client.call("message.send", {...}) # → ConnectionError("client is not connected")
162
-
163
- # ✅ 正确:重新认证并启用自动重连
164
- auth = await client.auth.authenticate({"aid": MY_AID})
165
- await client.connect(auth, {
166
- "auto_reconnect": True,
167
- })
136
+ await client.call("message.send", params) # ready 状态会抛 ConnectionError / StateError
168
137
  ```
138
+
139
+ 发送前检查:
140
+
141
+ ```python
142
+ if not client.can_send:
143
+ await client.connect({"auto_reconnect": True})
144
+ ```
145
+
146
+ ### E2EE 解密失败
147
+
148
+ P2P 或群消息解密失败通常由 prekey 不匹配、AAD 篡改、密文损坏或群 epoch 不一致引起。SDK 会发布 `message.undecryptable` / `group.message_undecryptable` 事件,应用可记录并继续处理其他消息。
@@ -1,104 +1,117 @@
1
- # AUN SDK Python - 最佳实践
1
+ # AUN SDK - 最佳实践
2
2
 
3
3
  ---
4
4
 
5
- ## 1. 幂等的连接初始化
5
+ ## 1. 幂等加载身份并连接
6
6
 
7
- 每次启动时安全地确保已认证并连接,无论是首次还是重复运行:
7
+ ```python
8
+ async def ensure_ready(aid: str) -> AUNClient:
9
+ store = AIDStore(aun_path="~/.aun/myapp", encryption_seed="")
10
+
11
+ loaded = store.load(aid)
12
+ if not loaded["ok"]:
13
+ registered = await store.register(aid)
14
+ if not registered["ok"]:
15
+ raise RuntimeError(registered["error"]["message"])
16
+ loaded = store.load(aid)
17
+
18
+ me = loaded["data"]["aid"]
19
+ client = AUNClient(me, debug=True)
20
+ await client.connect({"slot_id": "main", "auto_reconnect": True})
21
+ return client
22
+ ```
23
+
24
+ 要点:
25
+
26
+ - 先 `load()`,只有本地身份不存在时才 `register()`。
27
+ - 注册后重新 `load()`,不要把字符串 AID 直接传给 `AUNClient`。
28
+ - 连接前先订阅关键事件,避免漏掉首个状态变更或消息推送。
29
+
30
+ ---
31
+
32
+ ## 2. 多 AID 管理
8
33
 
9
34
  ```python
10
- async def ensure_connected(client: AUNClient, aid: str) -> str:
11
- try:
12
- await client.auth.register_aid({"aid": aid})
13
- except Exception as e:
14
- print(f"注册 AID 失败: {e}")
15
- raise
16
- auth = await client.auth.authenticate({"aid": aid})
17
- await client.connect(auth, {"auto_reconnect": True})
18
- return aid
35
+ store = AIDStore(aun_path="~/.aun/myapp", encryption_seed="")
36
+ alice = store.load("alice.agentid.pub")["data"]["aid"]
37
+ bob = store.load("bob.agentid.pub")["data"]["aid"]
38
+
39
+ alice_client = AUNClient(alice)
40
+ bob_client = AUNClient(bob)
19
41
  ```
20
42
 
21
- ## 2. 安全关闭多个客户端
43
+ 一个 `aun_path` 可管理多个 AID。不要把 `aun_path` 命名为某个 AID,否则会产生冗余嵌套路径。
44
+
45
+ ---
46
+
47
+ ## 3. 安全关闭
22
48
 
23
49
  ```python
24
50
  async def close_all(*clients: AUNClient):
25
- for c in clients:
51
+ for client in clients:
26
52
  try:
27
- await c.close()
53
+ await client.close()
28
54
  except Exception:
29
55
  pass
30
56
  ```
31
57
 
32
- ## 3. E2EE 幂等运行
58
+ `close()` 后客户端进入 `closed` 状态。若要复用对象,必须重新 `load_identity(AID对象)`。
33
59
 
34
- 每次运行前清除旧 prekey 缓存并跳过历史消息,避免计数器冲突:
60
+ ---
61
+
62
+ ## 4. E2EE 幂等运行
35
63
 
36
64
  ```python
37
- # 清除旧 prekey 缓存
38
65
  sender.e2ee.invalidate_prekey_cache(peer_aid=receiver_aid)
39
66
  receiver.e2ee.invalidate_prekey_cache(peer_aid=sender_aid)
40
67
 
41
- # 跳过旧消息(获取当前最大 seq 作为 cursor 起点)
42
- r = await receiver.call("message.pull", {"after_seq": 0, "limit": 1})
43
- recv_cursor = r.get("latest_seq", 0)
44
- ```
45
-
46
- ## 4. 多 AID 管理
47
-
48
- 一个 `aun_path` 可管理多个 AID,每个 AID 数据隔离在 `{aun_path}/AIDs/{aid}/` 下:
49
-
50
- ```python
51
- # 推荐:用应用名作为 aun_path,多个 AID 共存于同一目录
52
- client = AUNClient({"aun_path": "~/.aun/myapp"})
53
-
54
- # 注册不同的 AID,各自数据自动隔离
55
- await client.auth.register_aid({"aid": "alice.agentid.pub"})
56
- await client.auth.register_aid({"aid": "bob.agentid.pub"})
57
- # 数据分别在 ~/.aun/myapp/AIDs/alice.agentid.pub/ 和 bob.agentid.pub/ 下
68
+ cursor = await receiver.call("message.pull", {"after_seq": 0, "limit": 1})
69
+ recv_cursor = cursor.get("latest_seq", 0)
58
70
  ```
59
71
 
60
- > **注意**:不要用 AID 名称作为 `aun_path`(如 `~/.aun/{aid}`),否则会产生冗余嵌套。
61
-
62
- ## 5. 环境变量驱动配置
72
+ 测试和 demo 中建议跳过历史消息,避免旧消息、旧 prekey、旧游标影响当前断言。
63
73
 
64
- ```python
65
- import os
66
- from pathlib import Path
74
+ ---
67
75
 
68
- DATA_ROOT = os.environ.get("AUN_DATA_ROOT", str(Path.home() / ".aun"))
69
- ```
76
+ ## 5. protected_headers
70
77
 
71
- ## 6. 资源清理
78
+ 实例级 `protected_headers` 适合放 SDK 版本、运行环境、调用方链路标识等需要签名保护的元数据:
72
79
 
73
80
  ```python
74
- async def main():
75
- client = AUNClient()
76
- try:
77
- await ensure_connected(client, aid)
78
- # ... 业务逻辑
79
- finally:
80
- await client.close()
81
+ client = AUNClient(me, protected_headers={"sdk": "python", "app": "demo"})
82
+ client.set_protected_headers({"sdk": "python", "trace": "abc"})
81
83
  ```
82
84
 
83
- ## 参考
84
-
85
- - 协议文档:`../src/aun_core/docs/protocol/`
86
- - SDK 架构文档:`../src/aun_core/docs/skill/sdk-core/`
87
- - RPC 方法手册:`../src/aun_core/docs/skill/rpc-manual/`
88
- - 可运行示例:`../src/aun_core/docs/skill/examples/`
89
- - GitHub:https://github.com/ModelUnion/aun-sdk-core
85
+ 只对 `message.send`、`group.send`、`message.thought.put`、`group.thought.put` 生效。
90
86
 
91
87
  ---
92
88
 
93
- ## 7. Flow Control(v0.3.4+)
89
+ ## 6. Flow Control
94
90
 
95
91
  SDK 内部自动管理 RPC 并发,应用层无需配置:
96
92
 
97
93
  | 机制 | 说明 |
98
94
  |------|------|
99
95
  | RPC 并发上限 | 全局最多 16 个并发 RPC 请求 |
100
- | 后台 RPC 限制 | 后台任务(prekey 上传、token 刷新等)额外限制为 8 个 |
101
- | Pull Gate | 同一 namespace/group 的 pull 操作自动序列化,防止并发 pull 导致重复消息 |
102
- | 队列超时 | 排队等待超过 timeout 时抛 `TimeoutError("rpc queue timeout before send: {method}")` |
96
+ | 后台 RPC 限制 | 后台任务额外限制为 8 个 |
97
+ | Pull Gate | 同一 namespace/group 的 pull 操作自动序列化 |
98
+ | 队列超时 | 排队超过 timeout `TimeoutError` |
99
+
100
+ 应用层保持普通 `await client.call(...)` 即可。
103
101
 
104
- 这些限制在高并发场景下保护服务端不被单客户端打满,同时避免 pull 竞态导致的消息乱序。应用层只需正常调用 `client.call()`,SDK 自动排队和限流。
102
+ ---
103
+
104
+ ## 7. 测试数据保护
105
+
106
+ - 不要删除 `AIDs/` 下的私钥、证书、seed、数据库或 token 文件。
107
+ - 不要并行跑共享同一身份材料的集成 / E2E / 跨域测试。
108
+ - 需要换身份时使用新的 AID 名称,避免制造不可恢复的 key mismatch。
109
+
110
+ ---
111
+
112
+ ## 参考
113
+
114
+ - 协议文档:`../src/aun_core/docs/protocol/`
115
+ - RPC 方法手册:`09-*-rpc-manual.md`
116
+ - E2EE 说明:[05-E2EE加密通信.md](05-E2EE加密通信.md)
117
+ - API 手册:[06-API手册.md](06-API手册.md)
@@ -2,7 +2,7 @@
2
2
 
3
3
  AID Custody 是 AUN AP 可选提供的 AID 备份、恢复与跨设备复制服务,不属于 AUN 核心协议强制能力。当前阶段定义两条主流程:通过手机号和验证码上传、下载 AID 证书以及客户端加密后的私钥文件;以及旧设备通过 AID token 授权,新设备凭一次性复制码领取 OTP 加密材料的跨设备复制流程。
4
4
 
5
- 用户也可以部署自己的 AID 托管服务。只要服务地址发现格式和本手册定义的 HTTP 接口、请求响应语义保持一致,SDK 即可通过 `client.custody.set_url(...)` well-known 自动发现接入自部署服务。
5
+ 用户也可以部署自己的 AID 托管服务。只要服务地址发现格式和本手册定义的 HTTP 接口、请求响应语义保持一致,SDK 即可通过 custody 辅助客户端或 well-known 自动发现接入自部署服务。
6
6
 
7
7
  核心原则:
8
8
 
@@ -19,8 +19,9 @@ https://aid_custody.{issuer_domain}:{port}
19
19
  SDK 使用约束:
20
20
 
21
21
  - `custody_url` 不作为 `AUNClient` 构造配置参数传入。
22
- - 客户端通过 `client.custody.set_url(...)` 显式配置托管服务地址。
23
- - 也可以通过 `client.custody.discover_url(aid=...)` `https://{aid}/.well-known/aun-custody` 自动发现官方托管服务地址;发现结果会缓存在当前 `client.custody` 实例中。
22
+ - Python SDK 不在 `AUNClient` 上挂载 custody namespace;需要时使用独立 custody 辅助客户端或直接按本手册 HTTP API 调用。
23
+ - TS / JS / Go 仍保留历史 custody namespace 兼容层;新代码应避免把 custody 当作 AUNClient 核心会话能力。
24
+ - 可通过 `https://{aid}/.well-known/aun-custody` 自动发现官方托管服务地址,并缓存发现结果。
24
25
 
25
26
  ## 手机号验证码备份/恢复时序
26
27
 
@@ -94,9 +95,9 @@ SDK 方法:
94
95
 
95
96
  | 语言 | 发起复制 | 上传复制材料 | 领取复制材料 |
96
97
  |------|----------|--------------|--------------|
97
- | Python | `client.custody.create_device_copy(...)` | `client.custody.upload_device_copy_materials(...)` | `client.custody.claim_device_copy(...)` |
98
- | JS/TS | `client.custody.createDeviceCopy(...)` | `client.custody.uploadDeviceCopyMaterials(...)` | `client.custody.claimDeviceCopy(...)` |
99
- | Go | `client.Custody.CreateDeviceCopy(...)` | `client.Custody.UploadDeviceCopyMaterials(...)` | `client.Custody.ClaimDeviceCopy(...)` |
98
+ | Python | 独立 custody 辅助客户端或 HTTP API | 独立 custody 辅助客户端或 HTTP API | 独立 custody 辅助客户端或 HTTP API |
99
+ | JS/TS | 历史 custody namespace 兼容层或 HTTP API | 历史 custody namespace 兼容层或 HTTP API | 历史 custody namespace 兼容层或 HTTP API |
100
+ | Go | 历史 custody namespace 兼容层或 HTTP API | 历史 custody namespace 兼容层或 HTTP API | 历史 custody namespace 兼容层或 HTTP API |
100
101
 
101
102
  复制请求:
102
103
 
@@ -74,7 +74,7 @@ print(f"模式: {status['mode']}")
74
74
 
75
75
  该 RPC 适合已连接客户端查询。首次信任根更新应优先使用公开 HTTP 端点 `GET https://trust.aun.network/.well-known/aun/trust-roots.json`,不可达时可使用 Issuer PKI 泛域名端点 `GET https://pki.{issuer}/trust-root.json` 或 Gateway 镜像 `GET https://gateway.{issuer}/pki/trust-roots.json`。无论来源是 RPC 还是 HTTP,客户端导入前都必须验证 `authority_signature`。
76
76
 
77
- Issuer PKI 泛域名服务还必须公开 `GET https://pki.{issuer}/root.crt`,用于下载该 issuer 证书链锚定的 Root CA PEM。客户端通过 `client.meta.update_issuer_root_cert(issuer)` 更新指定 issuer 的根证书时,必须先确认该证书指纹存在于已验签的受信根列表中。
77
+ Issuer PKI 泛域名服务还必须公开 `GET https://pki.{issuer}/root.crt`,用于下载该 issuer 证书链锚定的 Root CA PEM。SDK `AIDStore.load()` / `resolve()` 的证书链验证流程中按需更新指定 issuer 的根证书;写入本地信任锚前必须确认该证书指纹存在于已验签的受信根列表中。
78
78
 
79
79
  ### 参数
80
80
 
@@ -120,23 +120,22 @@ Issuer PKI 泛域名服务还必须公开 `GET https://pki.{issuer}/root.crt`,
120
120
  ### 示例
121
121
 
122
122
  ```python
123
- trust_list = await client.meta.trust_roots()
124
- client.meta.import_trust_roots(trust_list, authority_cert_pem=authority_cert_pem)
123
+ trust_list = await client.call("meta.trust_roots", {})
124
+ # 信任根验证、导入和 issuer root 更新由 AIDStore 内部证书链验证流程按需处理。
125
125
  ```
126
126
 
127
127
  ---
128
128
 
129
- ## Python SDK `MetaNamespace` 辅助方法
129
+ ## SDK 信任根辅助能力
130
130
 
131
- 以下方法属于 SDK 本地辅助能力,不是新的服务端 RPC;底层只在需要时调用 `meta.trust_roots` 或公开 HTTP 端点。
131
+ 以下能力属于 SDK 本地证书链验证流程,不是新的服务端 RPC;底层只在需要时调用 `meta.trust_roots` 或公开 HTTP 端点。
132
132
 
133
- | 方法 | 说明 |
133
+ | 能力 | 说明 |
134
134
  |------|------|
135
- | `await client.meta.download_trust_roots(...)` | 从管理局权威端点、`pki.{issuer}` 或 Gateway 镜像下载受信根列表 |
136
- | `client.meta.verify_trust_roots(...)` | 验证受信根列表结构、签名、证书 CA 约束、有效期和 SHA-256 指纹 |
137
- | `client.meta.import_trust_roots(...)` | 验证后写入本地 `trust-roots.json` / `trust-roots.pem` 并刷新信任根缓存 |
138
- | `await client.meta.refresh_trust_roots(...)` | 下载、验证并导入受信根列表 |
139
- | `await client.meta.download_issuer_root_cert(issuer, ...)` | `https://pki.{issuer}/root.crt` 下载指定 issuer 的 Root CA PEM |
140
- | `await client.meta.update_issuer_root_cert(issuer, ...)` | 校验证书为自签 Root CA,且指纹存在于已验签受信根列表后导入本地 |
141
-
142
- `update_issuer_root_cert()` 不信任下载来源本身;它必须以已验签的受信根列表为准,确认 `root.crt` 的 SHA-256 指纹已列入 `root_cas` 后才能写入本地信任锚。
135
+ | 下载受信根列表 | 从管理局权威端点、`pki.{issuer}` 或 Gateway 镜像下载 |
136
+ | 验证受信根列表 | 校验结构、签名、证书 CA 约束、有效期和 SHA-256 指纹 |
137
+ | 导入受信根列表 | 验证后写入本地 `trust-roots.json` / `trust-roots.pem` 并刷新缓存 |
138
+ | 下载 issuer root | 从 `https://pki.{issuer}/root.crt` 下载指定 issuer 的 Root CA PEM |
139
+ | 更新 issuer root | 校验证书为自签 Root CA,且指纹存在于已验签受信根列表后导入本地 |
140
+
141
+ 更新 issuer root 时不信任下载来源本身;必须以已验签的受信根列表为准,确认 `root.crt` 的 SHA-256 指纹已列入 `root_cas` 后才能写入本地信任锚。
@@ -13,6 +13,8 @@
13
13
  | [storage.list_objects](#storagelist_objects) | 列举对象 |
14
14
  | [storage.list_prefixes](#storagelist_prefixes) | 列举子目录 |
15
15
  | [storage.get_quota](#storageget_quota) | 查询配额 |
16
+ | [storage.get_limits](#storageget_limits) | 查询上传限制 |
17
+ | [storage.check_upload](#storagecheck_upload) | 上传预检(秒传检测 + 超限检测) |
16
18
 
17
19
  ### 数据面协调方法
18
20
 
@@ -397,6 +399,93 @@ for obj in result["items"]:
397
399
 
398
400
  ---
399
401
 
402
+ ## storage.get_limits
403
+
404
+ 查询当前用户的上传限制和配额使用情况。客户端可在上传前调用此方法,避免超限后浪费流量。
405
+
406
+ ### 参数
407
+
408
+ | 参数 | 类型 | 必填 | 说明 |
409
+ |------|------|------|------|
410
+ | `owner_aid` | string | 否 | 查询指定用户的配额,默认当前用户 |
411
+
412
+ ### 响应
413
+
414
+ | 字段 | 类型 | 说明 |
415
+ |------|------|------|
416
+ | `max_inline_bytes` | integer | `put_object` 内联上限(当前 64KB) |
417
+ | `max_file_size_bytes` | integer | 单文件大小上限(当前 10MB) |
418
+ | `quota_total_bytes` | integer | 用户总配额(0 表示无限制) |
419
+ | `quota_used_bytes` | integer | 已用配额 |
420
+
421
+ ### 示例
422
+
423
+ ```python
424
+ limits = await client.call("storage.get_limits", {})
425
+ print(f"单文件上限: {limits['max_file_size_bytes']} bytes")
426
+ print(f"配额: {limits['quota_used_bytes']}/{limits['quota_total_bytes']}")
427
+ ```
428
+
429
+ ---
430
+
431
+ ## storage.check_upload
432
+
433
+ 上传预检:一次调用同时回答"文件是否超限"和"是否可秒传"。客户端应在计算完文件 SHA-256 后、实际上传前调用。
434
+
435
+ ### 参数
436
+
437
+ | 参数 | 类型 | 必填 | 说明 |
438
+ |------|------|------|------|
439
+ | `sha256` | string | 是 | 文件内容的 SHA-256 hex(64 字符) |
440
+ | `size_bytes` | integer | 是 | 文件大小(字节) |
441
+
442
+ ### 响应
443
+
444
+ | 字段 | 类型 | 说明 |
445
+ |------|------|------|
446
+ | `within_limit` | boolean | 文件大小是否在限制内 |
447
+ | `exists` | boolean | 服务端是否已有相同内容 |
448
+ | `skip_upload` | boolean | 是否可跳过上传(秒传) |
449
+
450
+ ### 使用场景
451
+
452
+ ```python
453
+ import hashlib
454
+
455
+ data = open("large_file.bin", "rb").read()
456
+ sha256 = hashlib.sha256(data).hexdigest()
457
+
458
+ check = await client.call("storage.check_upload", {
459
+ "sha256": sha256,
460
+ "size_bytes": len(data),
461
+ })
462
+
463
+ if not check["within_limit"]:
464
+ print("文件超限,无法上传")
465
+ elif check["skip_upload"]:
466
+ # 秒传:服务端已有相同内容,跳过上传直接 complete
467
+ await client.call("storage.complete_upload", {
468
+ "object_key": "my/file.bin",
469
+ "sha256": sha256,
470
+ "size_bytes": len(data),
471
+ "skip_blob": True,
472
+ })
473
+ else:
474
+ # 正常上传流程
475
+ session = await client.call("storage.create_upload_session", {
476
+ "object_key": "my/file.bin",
477
+ "size_bytes": len(data),
478
+ })
479
+ # HTTP PUT ...
480
+ await client.call("storage.complete_upload", {
481
+ "object_key": "my/file.bin",
482
+ "sha256": sha256,
483
+ "size_bytes": len(data),
484
+ })
485
+ ```
486
+
487
+ ---
488
+
400
489
  ## 错误码
401
490
 
402
491
  | code | 说明 |