@agentunion/fastaun-browser 0.2.19 → 0.2.20

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 (81) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/_packed_docs/CHANGELOG.md +26 -0
  3. package/_packed_docs/agent.md/SCHEMA.md +173 -0
  4. package/_packed_docs/agent.md/examples/codeagent-claudecode.md +61 -0
  5. package/_packed_docs/agent.md/examples/human-developer.md +60 -0
  6. package/_packed_docs/agent.md/examples/openclaw-lobster.md +52 -0
  7. package/_packed_docs/agent.md/examples/signed-openclaw-lobster.md +43 -0
  8. package/_packed_docs/protocol/00-/346/200/273/350/247/210/344/270/216/345/210/206/345/261/202.md +205 -0
  9. package/_packed_docs/protocol/00A-/350/256/276/350/256/241/345/216/237/345/210/231-/344/270/272Agent/350/200/214/347/224/237.md +197 -0
  10. package/_packed_docs/protocol/01-/350/272/253/344/273/275/344/270/216/345/207/255/350/257/201/345/215/217/350/256/256-auth.md +549 -0
  11. package/_packed_docs/protocol/02-/350/257/201/344/271/246/344/270/216/344/277/241/344/273/273/344/275/223/347/263/273.md +810 -0
  12. package/_packed_docs/protocol/03-Gateway-/350/277/236/346/216/245/346/250/241/345/274/217.md +262 -0
  13. package/_packed_docs/protocol/04-Peer-/345/255/220/345/215/217/350/256/256.md +180 -0
  14. package/_packed_docs/protocol/05-Relay-/345/255/220/345/215/217/350/256/256.md +164 -0
  15. package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +1135 -0
  16. package/_packed_docs/protocol/07-/351/224/231/350/257/257/347/240/201/344/270/216/347/212/266/346/200/201/346/234/272.md +234 -0
  17. package/_packed_docs/protocol/08-AUN-E2EE-Group.md +900 -0
  18. package/_packed_docs/protocol/08-AUN-E2EE.md +413 -0
  19. package/_packed_docs/protocol/09-/345/256/211/345/205/250/350/200/203/350/231/221.md +316 -0
  20. package/_packed_docs/protocol/10-Group-/345/255/220/345/215/217/350/256/256.md +804 -0
  21. package/_packed_docs/protocol/11-Storage-/345/255/220/345/215/217/350/256/256.md +271 -0
  22. package/_packed_docs/protocol/12-Stream-/345/255/220/345/215/217/350/256/256.md +329 -0
  23. package/_packed_docs/protocol/13-Agent/350/241/214/344/270/272/350/247/204/350/214/203.md +141 -0
  24. package/_packed_docs/protocol/14-/344/272/244/344/272/222/346/234/272/345/210/266-/345/223/215/345/272/224/346/250/241/345/274/217/344/270/216/350/207/252/344/270/273/346/250/241/345/274/217.md +170 -0
  25. package/_packed_docs/protocol/README.md +71 -0
  26. package/_packed_docs/protocol/agent.md/SCHEMA.md +118 -0
  27. package/_packed_docs/protocol/agent.md/examples/codeagent-claudecode.md +61 -0
  28. package/_packed_docs/protocol/agent.md/examples/human-developer.md +60 -0
  29. package/_packed_docs/protocol/agent.md/examples/openclaw-lobster.md +52 -0
  30. package/_packed_docs/protocol/aun-docs-guide.md +49 -0
  31. package/_packed_docs/protocol/index.md +114 -0
  32. package/_packed_docs/protocol//350/215/211/346/241/210-agent.md/347/255/276/345/220/215/345/215/217/350/256/256.md +205 -0
  33. package/_packed_docs/protocol//350/215/211/346/241/210-/346/213/222/347/273/235/344/277/241/345/217/267/345/215/217/350/256/256.md +249 -0
  34. package/_packed_docs/protocol//351/231/204/345/275/225A-/346/234/257/350/257/255/350/241/250.md +337 -0
  35. package/_packed_docs/protocol//351/231/204/345/275/225B-/346/211/251/345/261/225/346/200/247/346/214/207/345/215/227.md +80 -0
  36. package/_packed_docs/protocol//351/231/204/345/275/225C-/347/247/201/351/222/245/347/256/241/347/220/206/344/270/216/350/272/253/344/273/275/346/201/242/345/244/215.md +704 -0
  37. package/_packed_docs/protocol//351/231/204/345/275/225D-Root_CA_/346/262/273/347/220/206/346/234/272/345/210/266.md +620 -0
  38. package/_packed_docs/protocol//351/231/204/345/275/225E-Root_CA_/345/207/206/345/205/245/346/265/201/347/250/213.md +605 -0
  39. package/_packed_docs/protocol//351/231/204/345/275/225F-Issuer_CA_/347/224/263/350/257/267/346/265/201/347/250/213.md +548 -0
  40. package/_packed_docs/protocol//351/231/204/345/275/225G-AID_/345/255/244/345/204/277/351/242/204/351/230/262/344/270/216/346/225/221/346/217/264/346/234/272/345/210/266.md +513 -0
  41. package/_packed_docs/protocol//351/231/204/345/275/225H-Identity/346/234/215/345/212/241/345/256/236/347/216/260/346/214/207/345/215/227.md +619 -0
  42. package/_packed_docs/protocol//351/231/204/345/275/225I-/350/267/250/345/237/237/346/266/210/346/201/257/350/267/257/347/224/261/345/256/236/347/216/260/346/214/207/345/215/227.md +492 -0
  43. package/_packed_docs/protocol//351/231/204/345/275/225J-/345/256/242/346/210/267/347/253/257/346/216/245/345/205/245/347/244/272/344/276/213.md +402 -0
  44. package/_packed_docs/protocol//351/231/204/345/275/225K-Agent_Web/345/217/221/347/216/260/345/215/217/350/256/256.md +130 -0
  45. package/_packed_docs/protocol//351/231/204/345/275/225L-E2EE/345/256/236/347/216/260/346/214/207/345/215/227.md +267 -0
  46. package/_packed_docs/protocol//351/231/204/345/275/225M-JWT/350/256/244/350/257/201/345/256/236/347/216/260/346/214/207/345/215/227.md +367 -0
  47. package/_packed_docs/sdk/01-/345/277/253/351/200/237/345/274/200/345/247/213.md +223 -0
  48. package/_packed_docs/sdk/02-WebSocket/345/215/217/350/256/256.md +354 -0
  49. package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +172 -0
  50. package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +373 -0
  51. package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +611 -0
  52. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +1152 -0
  53. package/_packed_docs/sdk/07-/351/224/231/350/257/257/345/244/204/347/220/206.md +150 -0
  54. package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +89 -0
  55. package/_packed_docs/sdk/09-custody-api-manual.md +445 -0
  56. package/_packed_docs/sdk/09-group-rpc-manual.md +1895 -0
  57. package/_packed_docs/sdk/09-message-rpc-manual.md +597 -0
  58. package/_packed_docs/sdk/09-meta-rpc-manual.md +142 -0
  59. package/_packed_docs/sdk/09-payload-reference.md +702 -0
  60. package/_packed_docs/sdk/09-storage-rpc-manual.md +408 -0
  61. package/_packed_docs/sdk/09-stream-rpc-manual.md +275 -0
  62. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +72 -0
  63. package/_packed_docs/sdk/INDEX.md +131 -0
  64. package/_packed_docs/sdk/README.md +307 -0
  65. package/dist/auth.d.ts +2 -1
  66. package/dist/auth.d.ts.map +1 -1
  67. package/dist/auth.js +13 -11
  68. package/dist/auth.js.map +1 -1
  69. package/dist/client.d.ts +38 -8
  70. package/dist/client.d.ts.map +1 -1
  71. package/dist/client.js +179 -97
  72. package/dist/client.js.map +1 -1
  73. package/dist/namespaces/auth.d.ts +1 -0
  74. package/dist/namespaces/auth.d.ts.map +1 -1
  75. package/dist/namespaces/auth.js +20 -6
  76. package/dist/namespaces/auth.js.map +1 -1
  77. package/dist/transport.d.ts +9 -1
  78. package/dist/transport.d.ts.map +1 -1
  79. package/dist/transport.js +24 -0
  80. package/dist/transport.js.map +1 -1
  81. package/package.json +40 -37
@@ -0,0 +1,1152 @@
1
+ # AUN SDK Python - API 手册
2
+
3
+ ---
4
+
5
+ ## 目录
6
+
7
+ ### [AUNClient](#aunclient)
8
+ - [构造函数](#构造函数)
9
+ - [属性](#属性)
10
+ - [connect()](#await-connectauth-dict-options-dict--none---none) - 建立连接
11
+ - [call()](#await-callmethod-str-params-dict--none---any) - 调用 RPC 方法
12
+ - [on()](#onevent-str-handler-callable---subscription) - 订阅事件
13
+ - [off()](#offevent-str-handler-callable---none) - 注销事件处理器
14
+ - [close()](#await-close---none) - 关闭连接
15
+ - [disconnect()](#await-disconnect---none) - 断开连接(可重连)
16
+ - [list_identities()](#list_identities---listdict) - 列出本地身份
17
+ - [ping()](#await-pingparams-dict--none---any) - 连通性探测
18
+ - [set_local_agent_md_path()](#set_local_agent_md_pathpath-str---str--agentmd-版本一致性) - 配置本地 agent.md 路径,与服务端 etag 比对
19
+ - [status()](#await-statusparams-dict--none---any) - 网关状态查询
20
+ - [check_gateway_health()](#await-check_gateway_healthgateway_url-str-timeout-float--50---bool) - 检查网关可用性
21
+
22
+ ### [AUNClient.Auth](#authnamespace-clientauth)
23
+ - [create_aid()](#await-create_aidparams-dict---dict) - 注册新 AID
24
+ - [authenticate()](#await-authenticateparams-dict--none---dict) - 认证获取令牌
25
+ - [sign_agent_md()](#await-sign_agent_mdcontent-str-aid-str--none---str) - 为 agent.md 生成尾部签名
26
+ - [verify_agent_md()](#await-verify_agent_mdcontent-str-aid-str--none-cert_pem-str--none---dict) - 验证 agent.md 尾部签名
27
+ - [upload_agent_md()](#await-upload_agent_mdcontent-str---dict) - 上传自己的 agent.md
28
+ - [download_agent_md()](#await-download_agent_mdaid-str---str) - 下载指定 AID 的 agent.md
29
+ - [renew_cert()](#await-renew_certparams-dict--none---dict) - 续期证书
30
+ - [rekey()](#await-rekeyparams-dict--none---dict) - 密钥轮换
31
+ - [request_cert()](#await-request_certparams-dict---dict) - 通用证书请求
32
+ - [download_cert()](#await-download_certparams-dict--none---any) - 下载证书
33
+
34
+ ### [AUNClient.Meta](#metanamespace-clientmeta)
35
+ - [ping()](#await-clientmetapingparams-dict--none--none---any) - 连通性探测
36
+ - [status()](#await-clientmetastatusparams-dict--none--none---any) - 网关状态查询
37
+ - [trust_roots()](#await-clientmetatrust_rootsparams-dict--none--none---any) - 查询信任根
38
+ - [download_trust_roots()](#await-clientmetadownload_trust_rootsurl-str--none--none--gateway_url-str--none--none-timeout-float--100---dict) - 下载信任根列表
39
+ - [download_issuer_root_cert()](#await-clientmetadownload_issuer_root_certissuer-str-url-str--none--none-timeout-float--100---str) - 下载 issuer Root CA 证书
40
+ - [verify_trust_roots()](#clientmetaverify_trust_rootstrust_list-dict--authority_cert_pem-str--none--none-authority_public_key_pem-str--none--none-allow_unsigned-bool--false---dict) - 验证信任根列表
41
+ - [import_trust_roots()](#clientmetaimport_trust_rootstrust_list-dict--authority_cert_pem-str--none--none-authority_public_key_pem-str--none--none-allow_unsigned-bool--false---dict) - 验签并导入信任根
42
+ - [refresh_trust_roots()](#await-clientmetarefresh_trust_roots---dict) - 下载、验签并导入
43
+ - [update_issuer_root_cert()](#await-clientmetaupdate_issuer_root_certissuer-str---dict) - 更新指定 issuer Root CA 证书
44
+
45
+ ### [E2EEManager](#e2eemanager-cliente2ee)(高级 API,裸 WebSocket 开发者使用)
46
+ - [构造函数](#构造函数裸-websocket-开发者使用) - 独立实例化
47
+ - [encrypt_message()](#encrypt_messageto_aid-payload--peer_cert_pem-prekeynone---tupleany-bool) - 加密消息
48
+ - [decrypt_message()](#decrypt_messagemessage-dict---dict--none) - 解密单条消息(含本地防重放)
49
+ - [encrypt_outbound()](#encrypt_outboundpeer_aid-payload--peer_cert_pem-prekeynone-message_id-timestamp---tupleany-bool) - 加密出站消息(底层)
50
+ - [generate_prekey()](#generate_prekey---dict) - 生成 prekey 材料
51
+ - [cache_prekey()](#cache_prekeypeer_aid-prekey---none) - 缓存对方 prekey
52
+ - [get_cached_prekey()](#get_cached_prekeypeer_aid---dict--none) - 获取缓存的 prekey
53
+ - [invalidate_prekey_cache()](#invalidate_prekey_cachepeer_aid---none) - 使 prekey 缓存失效
54
+
55
+ ### [GroupE2EEManager](#groupe2eemanager-clientgroup_e2ee)(高级 API,裸 WebSocket 开发者使用)
56
+ - [构造函数](#构造函数群组-e2ee) - 独立实例化
57
+ - [create_epoch()](#create_epochgroup_id-member_aids---dict) - 创建首个 epoch
58
+ - [rotate_epoch()](#rotate_epochgroup_id-member_aids---dict) - 轮换 epoch
59
+ - [rotate_epoch_to()](#rotate_epoch_togroup_id-target_epoch-member_aids---dict) - 指定目标 epoch 轮换(配合 CAS)
60
+ - [encrypt()](#encryptgroup_id-payload--message_idnone-timestampnone---dict) - 加密群消息
61
+ - [decrypt()](#decryptmessage-dict---dict--none) - 解密单条群消息
62
+ - [decrypt_batch()](#decrypt_batchmessages---list) - 批量解密
63
+ - [handle_incoming()](#handle_incomingpayload-dict---str--none) - 处理 P2P 密钥消息
64
+ - [build_recovery_request()](#build_recovery_requestgroup_id-epoch--sender_aidnone---dict--none) - 构建密钥恢复请求
65
+ - [handle_key_request_msg()](#handle_key_request_msgrequest_payload-current_members---dict--none) - 处理密钥请求
66
+ - [has_secret()](#has_secretgroup_id---bool) / [current_epoch()](#current_epochgroup_id---int--none) / [get_member_aids()](#get_member_aidsgroup_id---list) - 状态查询
67
+
68
+ ### [其他](#其他)
69
+ - [Subscription](#subscription) - 事件订阅对象
70
+ - [内置事件](#内置事件) - 事件列表
71
+ - [RPC 方法参考](#rpc-方法参考) - 业务 RPC 手册链接
72
+
73
+ ### [Stream 使用指南](#stream-使用指南)
74
+ - [创建流](#创建流) - stream.create
75
+ - [推流](#推流websocket) - WebSocket 推送数据帧
76
+ - [拉流](#拉流http-sse) - HTTP SSE 接收数据
77
+ - [关闭流](#关闭流) - stream.close
78
+ - [查询流状态](#查询流状态) - stream.get_info / stream.list_active
79
+
80
+ ---
81
+
82
+ ## AUNClient
83
+
84
+ 主客户端类,所有操作的入口。
85
+
86
+ ### 构造函数
87
+
88
+ **`AUNClient(config: dict | None)`**
89
+
90
+ | 参数 | 类型 | 必填 | 默认值 | 说明 |
91
+ |------|------|------|--------|------|
92
+ | `aun_path` | `str` | 否 | `~/.aun` | 应用级数据目录(AID 数据在 `{aun_path}/AIDs/{aid}/` 下) |
93
+ | `root_ca_path` | `str` | 否 | `None` | 额外 Root CA 路径 |
94
+ | `seed_password` | `str` | 否 | `None` | 本地存储保护口令 |
95
+
96
+ ```python
97
+ client = AUNClient({
98
+ "aun_path": "~/.aun/myapp",
99
+ "seed_password": "seed",
100
+ })
101
+ ```
102
+
103
+ `verify_ssl` 不在构造阶段传入。Python / TS / Go SDK 根据 `AUN_ENV` 或 `KITE_ENV` 自动决定是否校验证书;Browser SDK 恒为 `true`。
104
+
105
+ ### 属性
106
+
107
+ | 属性 | 类型 | 说明 |
108
+ |------|------|------|
109
+ | `aid` | `str \| None` | 当前连接的 AID |
110
+ | `state` | `str` | 连接状态 (`idle` / `connecting` / `authenticating` / `connected` / `disconnected` / `reconnecting` / `terminal_failed` / `closed`) |
111
+ | `auth` | `AuthNamespace` | 认证命名空间 |
112
+ | `meta` | `MetaNamespace` | 元信息与信任根管理命名空间 |
113
+ | `e2ee` | `E2EEManager` | P2P E2EE 工具类 |
114
+ | `group_e2ee` | `GroupE2EEManager` | 群组 E2EE 工具类(当前 Python SDK 固定可用) |
115
+ | `gateway_health` | `bool \| None` | 最近一次 health check 结果,`None` 表示尚未检查 |
116
+
117
+ ---
118
+
119
+ ### `await connect(auth: dict, options: dict | None) -> None`
120
+
121
+ 建立 WebSocket 连接。必须先调用 `client.auth.authenticate()` 获取 `auth` 参数。
122
+
123
+ **参数 `auth`**(来自 `authenticate()` 返回值)
124
+
125
+ | 字段 | 类型 | 必填 | 说明 |
126
+ |------|------|------|------|
127
+ | `aid` | `str` | 是 | AID |
128
+ | `access_token` | `str` | 是 | 访问令牌 |
129
+ | `refresh_token` | `str` | 是 | 刷新令牌 |
130
+ | `expires_at` | `int` | 是 | 令牌过期时间戳(秒) |
131
+ | `gateway` | `str` | 是 | 网关 WebSocket URL |
132
+
133
+ **参数 `options`**
134
+
135
+ | 字段 | 类型 | 默认值 | 说明 |
136
+ |------|------|--------|------|
137
+ | `slot_id` | `str` | `""` | 同一设备上的实例槽位;空字符串表示该设备单实例模式 |
138
+ | `delivery_mode.mode` | `str` | `"fanout"` | 连接级投递语义;同一 AID 的所有在线实例必须保持一致 |
139
+ | `delivery_mode.routing` | `str` | `"round_robin"` | 仅 `queue` 模式有效 |
140
+ | `delivery_mode.affinity_ttl_ms` | `int` | `300000` | 仅 `queue + sender_affinity` 有效 |
141
+ | `auto_reconnect` | `bool` | `True` | 断线自动重连 |
142
+ | `heartbeat_interval` | `float` | `30.0` | 心跳间隔(秒) |
143
+ | `token_refresh_before` | `float` | `60.0` | 令牌过期前多久刷新(秒) |
144
+ | `connection_kind` | `str` | `"long"` | 连接类型:`"long"` = 长连接(收推送);`"short"` = 短连接(发 RPC 后断开) |
145
+ | `short_ttl_ms` | `int` | `0` | 仅 `kind="short"` 时有效,服务端兜底关闭超时(毫秒);0 = 不限时 |
146
+ | `retry.initial_delay` | `float` | `1.0` | 首次重连延迟(秒) |
147
+ | `retry.max_delay` | `float` | `64.0` | 最大重连延迟(秒) |
148
+ | `timeouts.connect` | `float` | `5.0` | 连接超时(秒) |
149
+ | `timeouts.call` | `float` | `10.0` | RPC 调用超时(秒) |
150
+ | `timeouts.http` | `float` | `30.0` | HTTP 请求超时(秒) |
151
+
152
+ > 当前实现只读取 `retry.initial_delay` / `retry.max_delay`;未提供 `retry.max_attempts` 选项。
153
+
154
+ ```python
155
+ auth = await client.auth.authenticate({"aid": MY_AID})
156
+ await client.connect(auth, {
157
+ "slot_id": "slot-a",
158
+ "delivery_mode": {"mode": "fanout"},
159
+ "auto_reconnect": True,
160
+ "heartbeat_interval": 30.0,
161
+ })
162
+ ```
163
+
164
+ **典型使用模式**
165
+
166
+ 长连接守护进程(常驻收件箱):
167
+
168
+ ```python
169
+ client = AUNClient({"aun_path": "/home/alice/.aun/alice"})
170
+ auth = await client.auth.authenticate({"aid": "alice.example.com"})
171
+ await client.connect(auth, {"connection_kind": "long", "slot_id": "main"})
172
+ client.on("message.received", handle)
173
+ await asyncio.Event().wait() # 常驻
174
+ ```
175
+
176
+ CLI 短连接(与长连接共享 keystore,自动复用 token):
177
+
178
+ ```python
179
+ client = AUNClient({"aun_path": "/home/alice/.aun/alice"}) # 同 path
180
+ auth = await client.auth.authenticate({"aid": "alice.example.com"}) # 命中 cached token
181
+ await client.connect(auth, {
182
+ "connection_kind": "short",
183
+ "slot_id": "main", # 与长连接同槽位共存
184
+ "short_ttl_ms": 30000,
185
+ })
186
+ await client.call("message.send", {...})
187
+ await client.close()
188
+ ```
189
+
190
+ 完整说明(token 复用机制、三种典型场景对比)见 [04-连接与认证.md](04-连接与认证.md#长连接--短连接代码示例)。
191
+
192
+ ---
193
+
194
+ ### `await call(method: str, params: dict | None) -> Any`
195
+
196
+ 调用 RPC 方法。内部保留方法(`auth.*`、`initialize` 等)不可通过此接口调用。
197
+
198
+ | 参数 | 类型 | 必填 | 说明 |
199
+ |------|------|------|------|
200
+ | `method` | `str` | 是 | RPC 方法名 |
201
+ | `params` | `dict` | 否 | 方法参数 |
202
+
203
+ **返回值**: 方法返回的结果(类型取决于具体方法)
204
+
205
+ **E2EE 自动加密/解密**:
206
+
207
+ - `message.send` 和 `group.send` **默认加密发送**(`encrypt` 默认 `True`),无需显式传参
208
+ - `message.thought.put` **强制 P2P E2EE 加密**,`encrypt=False` 会被拒绝
209
+ - `group.thought.put` **强制群组 E2EE 加密**,`encrypt=False` 会被拒绝
210
+ - 发送明文消息需显式传 `encrypt=False`
211
+ - `message.pull` / `group.pull` 返回的消息已自动解密,加密消息带有 `encrypted=True` 标记
212
+ - `message.thought.get` 返回前自动解密服务端密文 `items`,应用层读取 `thoughts[]`
213
+ - `group.thought.get` 返回前自动解密服务端密文 `items`,应用层读取 `thoughts[]`
214
+ - P2P 消息的投递语义由连接阶段声明的 `delivery_mode` 决定
215
+ - `group.send` 固定为 `fanout`,不支持 `queue`
216
+ - 群消息的 `dispatch_mode` 来自群设置,SDK 会在解密后保留顶层 `dispatch_mode` 并注入 `payload.dispatch_mode`
217
+ - `message.send` / `message.thought.put` / `group.send` / `group.thought.put` 可传 `protected_headers`,SDK 会为它和 thought `context` 生成独立 `_auth`,接收端验通过后在 `message.e2ee.protected_headers` / `message.e2ee.context` 暴露给应用层
218
+ - Python SDK 会为 `message.pull` / `message.ack` 自动附带当前实例的 `device_id` / `slot_id`,应用层不应手工覆盖
219
+
220
+ ```python
221
+ # 发送加密消息(默认行为,无需传 encrypt)
222
+ await client.call("message.send", {
223
+ "to": "bob.agentid.pub",
224
+ "payload": {"type": "text", "text": "秘密消息"},
225
+ })
226
+
227
+ # 接收并自动解密(SDK 会自动带当前实例的 device_id / slot_id)
228
+ result = await client.call("message.pull", {"after_seq": 0, "limit": 50})
229
+ for msg in result["messages"]:
230
+ print(msg["payload"]) # 加密消息已自动解密
231
+
232
+ # 发送明文消息(需显式关闭加密)
233
+ await client.call("message.send", {
234
+ "to": "bob.agentid.pub",
235
+ "payload": {"type": "text", "text": "Hello"},
236
+ "encrypt": False,
237
+ })
238
+ ```
239
+
240
+ **`message.send` 额外参数**:
241
+
242
+ | 字段 | 类型 | 必填 | 说明 |
243
+ |------|------|------|------|
244
+ | `encrypt` | `bool` | 否 | 是否加密消息(默认 `true`) |
245
+ | `message_id` | `str` | 否 | 消息 ID(不传则自动生成) |
246
+ | `timestamp` | `int` | 否 | 时间戳毫秒(不传则自动生成) |
247
+ | `protected_headers` / `headers` | `dict` / `ProtectedHeaders` | 否 | E2EE 信封元数据,类似 HTTP headers;SDK 自动补 `payload_type` 并做 `_auth` 防篡改 |
248
+
249
+ P2P 消息的 `delivery_mode` 由当前连接实例携带;应用层通过 `connect` 配置即可。
250
+
251
+ **`message.thought.put/get` 额外参数**:
252
+
253
+ | 字段 | 类型 | 必填 | 说明 |
254
+ |------|------|------|------|
255
+ | `to` | `str` | put 必填 | P2P 会话另一方 AID |
256
+ | `context.type` | `str` | 是 | 思考上下文类型,推荐 `run` |
257
+ | `context.id` | `str` | 是 | 思考上下文 ID,如 `run_id` |
258
+ | `payload` | `dict` | put 必填 | 思考内容,推荐 `{"type": "thought", "text": "..."}` |
259
+ | `sender_aid` | `str` | get 必填 | thought 作者 AID |
260
+ | `peer_aid` / `to` | `str` | 条件必填 | 读取自己写的 thought 时指定会话另一方 |
261
+ | `protected_headers` / `headers` | `dict` / `ProtectedHeaders` | put 可选 | E2EE 信封元数据;`context` 会另行绑定到信封内并验 `_auth` |
262
+
263
+ `message.thought.put/get` 只使用 `context.type + context.id` 定位 thought head。
264
+
265
+ ```python
266
+ await client.call("message.thought.put", {
267
+ "to": "bob.agentid.pub",
268
+ "context": {"type": "run", "id": "run-xxx"},
269
+ "payload": {"type": "thought", "text": "先核对约束"},
270
+ })
271
+
272
+ result = await client.call("message.thought.get", {
273
+ "sender_aid": "bob.agentid.pub",
274
+ "context": {"type": "run", "id": "run-xxx"},
275
+ })
276
+ ```
277
+
278
+ **ProtectedHeaders 读取位置**:
279
+
280
+ ```python
281
+ from aun_core import ProtectedHeaders
282
+
283
+ headers = ProtectedHeaders({"device_id": "dev-123"}).set("slot_id", "desktop")
284
+ await client.call("group.send", {
285
+ "group_id": "10001.example.com",
286
+ "payload": {"type": "text", "text": "群组消息"},
287
+ "protected_headers": headers,
288
+ })
289
+
290
+ pulled = await client.call("group.pull", {"group_id": "10001.example.com"})
291
+ received_headers = pulled["messages"][0].get("e2ee", {}).get("protected_headers", {})
292
+ ```
293
+
294
+ `payload_type` 由 SDK 根据加密前 `payload.type` 自动设置,应用层不需要传。完整安全语义见 [05-E2EE加密通信](05-E2EE加密通信.md#protectedheaders-与可验证上下文)。
295
+
296
+ **群消息 `dispatch_mode` 设置**:
297
+
298
+ ```python
299
+ await client.call("group.set_settings", {
300
+ "group_id": "g-abc123.agentid.pub",
301
+ "settings": {"dispatch_mode": "mention"},
302
+ })
303
+ ```
304
+
305
+ 后续 `group.send` / `group.pull` / `group.message_created` 中的群消息会携带 `dispatch_mode`,取值为 `"broadcast"` 或 `"mention"`。
306
+
307
+ ---
308
+
309
+ ### `on(event: str, handler: Callable) -> Subscription`
310
+
311
+ 订阅事件,支持同步和异步 handler。
312
+
313
+ | 参数 | 类型 | 必填 | 说明 |
314
+ |------|------|------|------|
315
+ | `event` | `str` | 是 | 事件名 |
316
+ | `handler` | `Callable` | 是 | 事件处理函数 |
317
+
318
+ **返回值**: `Subscription` 对象(可调用 `.unsubscribe()` 取消订阅)
319
+
320
+ > 事件处理器内部抛出的异常会被 SDK 记录并吞掉,不会中断其他处理器,也不会自动重新抛回到调用方。
321
+
322
+ ```python
323
+ sub = client.on("message.received", lambda e: print(e))
324
+ sub.unsubscribe()
325
+ ```
326
+
327
+ ---
328
+
329
+ ### `off(event: str, handler: Callable) -> None`
330
+
331
+ 注销指定的事件处理器。等价于通过 `Subscription.unsubscribe()` 取消订阅,但无需保留订阅句柄。
332
+
333
+ | 参数 | 类型 | 必填 | 说明 |
334
+ |------|------|------|------|
335
+ | `event` | `str` | 是 | 事件名 |
336
+ | `handler` | `Callable` | 是 | 之前传给 `on()` 的同一处理函数引用 |
337
+
338
+ > 若传入的 handler 未注册,调用无副作用(幂等)。
339
+
340
+ ```python
341
+ def handle_msg(e):
342
+ print(e)
343
+
344
+ client.on("message.received", handle_msg)
345
+ # ... 之后取消订阅
346
+ client.off("message.received", handle_msg)
347
+ ```
348
+
349
+ ---
350
+
351
+ ### `await close() -> None`
352
+
353
+ 关闭连接,停止心跳、令牌刷新、重连等所有后台任务。调用后客户端进入 `closed` 状态,不可再次 `connect()`。
354
+
355
+ ---
356
+
357
+ ### `await disconnect() -> None`
358
+
359
+ 断开 WebSocket 连接,但不销毁客户端。与 `close()` 的区别:`disconnect()` 后可再次调用 `connect()` 重新建立连接;`close()` 则彻底终止客户端生命周期。
360
+
361
+ 断开后状态变为 `disconnected`,并发布 `connection.state` 事件。若客户端已在 `closing` 流程中,调用无副作用。
362
+
363
+ ```python
364
+ await client.disconnect()
365
+ # 之后可重新连接
366
+ auth = await client.auth.authenticate({"aid": MY_AID})
367
+ await client.connect(auth)
368
+ ```
369
+
370
+ ---
371
+
372
+ ### `list_identities() -> list[dict]`
373
+
374
+ 返回本地 keystore 中所有已存储且拥有有效私钥的身份摘要列表(同步方法,无需 `await`)。
375
+
376
+ **返回值**: 每个元素结构为:
377
+
378
+ | 字段 | 类型 | 说明 |
379
+ |------|------|------|
380
+ | `aid` | `str` | AID |
381
+ | `metadata` | `dict` | 该 AID 的本地元数据(若存在) |
382
+
383
+ ```python
384
+ identities = client.list_identities()
385
+ for item in identities:
386
+ print(item["aid"])
387
+ ```
388
+
389
+ ---
390
+
391
+ ### `await ping(params: dict | None) -> Any`
392
+
393
+ 调用 `meta.ping` RPC,等价于 `await client.meta.ping(params)`。用于连通性探测或心跳检测。
394
+
395
+ ---
396
+
397
+ ### `set_local_agent_md_path(path: str) -> str` — agent.md 版本一致性
398
+
399
+ 记录本地 `agent.md` 文件路径并一次性计算 etag(带引号的 sha256 hex,与服务端 `_agent_md_etag` 算法严格一致)。
400
+
401
+ **用途:** 配合服务端 Gateway 在每次 RPC 响应注入的 `_meta.agent_md_etag`,让 SDK 应用层判断 "本地 agent.md 是否已发布到服务端 / 服务端是否有新版本"。
402
+
403
+ **API 跨语言对齐:**
404
+
405
+ | SDK | 设置方法 | 读本地 etag | 读远端 etag |
406
+ |------|---------|-----------|----------|
407
+ | Python | `client.set_local_agent_md_path(path)` | `client.get_local_agent_md_etag()` | `client.get_remote_agent_md_etag()` |
408
+ | TypeScript | `client.setLocalAgentMdPath(path)` | `client.getLocalAgentMdEtag()` | `client.getRemoteAgentMdEtag()` |
409
+ | Go | `client.SetLocalAgentMDPath(path) string` | `client.GetLocalAgentMDEtag() string` | `client.GetRemoteAgentMDEtag() string` |
410
+ | C++ | `client.SetLocalAgentMdPath(path)` | `client.GetLocalAgentMdEtag()` | `client.GetRemoteAgentMdEtag()` |
411
+ | JavaScript(浏览器) | `client.setLocalAgentMdContent(content)` | `client.getLocalAgentMdEtag()` | `client.getRemoteAgentMdEtag()` |
412
+
413
+ **JavaScript 特殊说明:** 浏览器无法读本地文件,改为接收文本内容直接计算 etag(业务侧可用 `<input type=file>` 读出文本传入)。TS SDK 在 Node 环境读文件,浏览器环境返回空串并 warn。
414
+
415
+ **返回值:** 当前 etag(形如 `"abc123..."` 带引号),文件不存在/读取失败时返回空串,**不抛异常**。
416
+
417
+ **应用层事件注入:** SDK 在 publish `message.received` / `group.message_created` 等应用事件时,会自动给 payload 加 `_agent_md` 字段:
418
+
419
+ ```python
420
+ {
421
+ "_agent_md": {
422
+ "local_etag": "\"abc...\"", # 本地 agent.md 的 etag
423
+ "remote_etag": "\"def...\"", # gateway 注入的服务端 etag
424
+ },
425
+ # ... 原有业务字段
426
+ }
427
+ ```
428
+
429
+ **典型用法:**
430
+
431
+ ```python
432
+ client = AUNClient()
433
+ client.set_local_agent_md_path("/path/to/agent.md") # 启动时调一次
434
+
435
+ await client.connect(auth)
436
+
437
+ @client.on("message.received")
438
+ async def on_msg(payload):
439
+ meta = payload.get("_agent_md", {})
440
+ if meta["local_etag"] and meta["remote_etag"] and meta["local_etag"] != meta["remote_etag"]:
441
+ # 本地与服务端不一致,提示用户重新上传
442
+ print("agent.md 已变化,请调用 client.auth.upload_agent_md() 同步")
443
+ ```
444
+
445
+ **注意事项:**
446
+ - 文件改了之后需要再次调用 `set_local_agent_md_path()` 触发重算(设计上一次性计算,不监听文件 mtime)
447
+ - 服务端 etag 来自 Gateway 缓存(5 分钟 TTL,上传后通过 kernel 事件立即失效)
448
+ - 注入失败被吞,**绝不影响业务路径**
449
+
450
+ ---
451
+
452
+ ### `await status(params: dict | None) -> Any`
453
+
454
+ 调用 `meta.status` RPC,等价于 `await client.meta.status(params)`。返回网关服务状态信息。
455
+
456
+ ---
457
+
458
+ ### `await check_gateway_health(gateway_url: str, timeout: float = 5.0) -> bool`
459
+
460
+ 基于传入的 Gateway WebSocket URL 动态构造健康检查地址:将末尾路径替换为 `/health`,并将 `wss://` / `ws://` 分别转换为 `https://` / `http://`。随后向该地址发送 `GET /health` 请求,检查网关可用性。结果同步更新 `gateway_health` 属性。
461
+
462
+ | 参数 | 类型 | 默认值 | 说明 |
463
+ |------|------|--------|------|
464
+ | `gateway_url` | `str` | — | 服务器发现返回的网关 WebSocket URL(`wss://.../aun` 或 `ws://.../aun`) |
465
+ | `timeout` | `float` | `5.0` | 超时秒数 |
466
+
467
+ 返回 `True` 表示网关可用(HTTP 200),`False` 表示不可用或超时。
468
+
469
+ > **说明**:`discover()` 成功后会自动异步触发一次 health check,无需手动调用。
470
+
471
+ **各语言对应 API**
472
+
473
+ | 语言 | 属性 | 方法 |
474
+ |------|------|------|
475
+ | Python | `client.gateway_health` | `await client.check_gateway_health(url)` |
476
+ | TypeScript | `client.gatewayHealth` | `await client.checkGatewayHealth(url)` |
477
+ | Go | `client.GatewayHealth()` | `client.CheckGatewayHealth(ctx, url, timeout)` |
478
+ | JS (browser) | `client.gatewayHealth` | `await client.checkGatewayHealth(url)` |
479
+
480
+ ---
481
+
482
+ ## AUNClient.Auth (`client.auth`)
483
+
484
+ ---
485
+
486
+ ### `await create_aid(params: dict) -> dict`
487
+
488
+ 注册新 AID,本地生成 ECDSA 密钥对并向 Gateway 申请 X.509 证书。
489
+
490
+ **参数**
491
+
492
+ | 字段 | 类型 | 必填 | 说明 |
493
+ |------|------|------|------|
494
+ | `aid` | `str` | 是 | 要注册的 AID |
495
+
496
+ **返回值**
497
+
498
+ | 字段 | 类型 | 说明 |
499
+ |------|------|------|
500
+ | `aid` | `str` | 已注册的 AID |
501
+ | `cert_pem` | `str` | X.509 证书(PEM 格式) |
502
+ | `gateway` | `str` | 网关 URL |
503
+
504
+ ```python
505
+ MY_AID = f"alice-{random.randint(1000,9999)}.agentid.pub"
506
+ result = await client.auth.create_aid({"aid": MY_AID})
507
+ # {"aid": "alice-XXXX.agentid.pub", "cert_pem": "-----BEGIN...", "gateway": "ws://..."}
508
+ ```
509
+
510
+ ---
511
+
512
+ ### `await authenticate(params: dict | None) -> dict`
513
+
514
+ 执行双向 ECDSA 挑战-响应认证,获取访问令牌。
515
+
516
+ **参数**
517
+
518
+ | 字段 | 类型 | 必填 | 说明 |
519
+ |------|------|------|------|
520
+ | `aid` | `str` | 否 | AID(可选,默认使用已加载的身份) |
521
+
522
+ **返回值**
523
+
524
+ | 字段 | 类型 | 说明 |
525
+ |------|------|------|
526
+ | `aid` | `str` | 认证的 AID |
527
+ | `access_token` | `str` | 访问令牌 |
528
+ | `refresh_token` | `str` | 刷新令牌 |
529
+ | `expires_at` | `int` | 令牌过期时间戳(秒) |
530
+ | `gateway` | `str` | 网关 WebSocket URL |
531
+
532
+ ```python
533
+ auth = await client.auth.authenticate({"aid": MY_AID})
534
+ ```
535
+
536
+ ---
537
+
538
+ ### `await sign_agent_md(content: str, aid: str | None = None) -> str`
539
+
540
+ 为 `agent.md` 生成尾部签名块。
541
+
542
+ **参数**
543
+
544
+ | 字段 | 类型 | 必填 | 说明 |
545
+ |------|------|------|------|
546
+ | `content` | `str` | 是 | 完整的 `agent.md` 文本(YAML frontmatter + Markdown 正文 + 可选尾部签名块) |
547
+ | `aid` | `str` | 否 | 指定要使用的本地身份 AID;不传则使用当前 AID |
548
+
549
+ **返回值**
550
+
551
+ 签名后的完整 `agent.md` 文本。
552
+
553
+ **说明**
554
+
555
+ - 若输入内容已经带有尾部签名块,会先剥离旧签名再重新签名
556
+ - 签名块位于文件尾部,签名内容只覆盖签名块之前的全部字节
557
+ - 签名块本身不参与验证时的 payload 计算
558
+
559
+ ---
560
+
561
+ ### `await verify_agent_md(content: str, aid: str | None = None, cert_pem: str | None = None) -> dict`
562
+
563
+ 验证 `agent.md` 尾部签名。
564
+
565
+ **参数**
566
+
567
+ | 字段 | 类型 | 必填 | 说明 |
568
+ |------|------|------|------|
569
+ | `content` | `str` | 是 | 完整的 `agent.md` 文本 |
570
+ | `aid` | `str` | 否 | 预期 AID;用于校验 payload 中的 `aid` 与证书归属 |
571
+ | `cert_pem` | `str` | 否 | 直接提供对端证书 PEM;不传时 SDK 会按 `aid + cert_fingerprint` 拉取 |
572
+
573
+ **返回值**
574
+
575
+ | 字段 | 类型 | 说明 |
576
+ |------|------|------|
577
+ | `status` | `str` | `verified` / `invalid` / `unsigned` |
578
+ | `verified` | `bool` | 是否验签成功 |
579
+ | `payload` | `str` | 去掉尾部签名块后的原始内容 |
580
+ | `reason` | `str` | 失败原因(仅 `invalid` 时可能存在) |
581
+ | `aid` | `str` | 关联 AID |
582
+ | `cert_fingerprint` | `str` | 使用的证书指纹 |
583
+ | `timestamp` | `int` | 签名时间戳 |
584
+
585
+ **说明**
586
+
587
+ - `unsigned` 表示文件未带签名块,不视为错误
588
+ - 若 `cert_pem` 未提供,SDK 会根据 `aid` 和签名块里的 `cert_fingerprint` 拉取对端证书再验签
589
+ - 验签失败不会抛异常,而是通过 `status=invalid` 返回原因
590
+
591
+ ---
592
+
593
+ ### `await upload_agent_md(content: str) -> dict`
594
+
595
+ 上传当前 AID 的公开 `agent.md` 文档。
596
+
597
+ **参数**
598
+
599
+ | 字段 | 类型 | 必填 | 说明 |
600
+ |------|------|------|------|
601
+ | `content` | `str` | 是 | 完整的 `agent.md` 文本(YAML frontmatter + Markdown 正文 + 可选尾部签名块) |
602
+
603
+ **返回值**
604
+
605
+ | 字段 | 类型 | 说明 |
606
+ |------|------|------|
607
+ | `aid` | `str` | 当前上传目标 AID |
608
+ | `bytes` | `int` | 文档字节数 |
609
+ | `etag` | `str` | 服务端返回的 ETag |
610
+ | `last_modified` | `str` | HTTP 日期格式的最后修改时间 |
611
+ | `agent_md_url` | `str` | 文档访问 URL |
612
+
613
+ **说明**
614
+
615
+ - 该方法会自动复用本地缓存的 access token;若 token 缺失或过期,会自动重新认证后再上传
616
+ - 对应 HTTP 端点:`PUT https://{aid}/agent.md`
617
+ - 上传需要 `Authorization: Bearer <access_token>`
618
+ - 常见错误:
619
+ `401` 表示缺失或无效 token
620
+ `403` 表示 token 的 `aid` 与目标 Host 不一致
621
+ `400` 表示 `agent.md` frontmatter 非法,或其中的 `aid` 与目标 Host 不一致
622
+ `413` 表示文档大小超过服务端限制
623
+ SDK 在这些场景下抛出 `AUNError`
624
+
625
+ ```python
626
+ result = await client.auth.upload_agent_md("""---
627
+ aid: alice.agentid.pub
628
+ name: Alice
629
+ ---
630
+
631
+ # Alice
632
+ """)
633
+ ```
634
+
635
+ ---
636
+
637
+ ### `await download_agent_md(aid: str) -> str`
638
+
639
+ 匿名下载指定 AID 的公开 `agent.md` 文档。
640
+
641
+ **参数**
642
+
643
+ | 字段 | 类型 | 必填 | 说明 |
644
+ |------|------|------|------|
645
+ | `aid` | `str` | 是 | 目标 AID |
646
+
647
+ **返回值**
648
+
649
+ 完整的 `agent.md` 文本。
650
+
651
+ **说明**
652
+
653
+ - 对应 HTTP 端点:`GET https://{aid}/agent.md`
654
+ - 若只需查询是否存在及缓存元数据,可直接使用 `HEAD https://{aid}/agent.md`
655
+ - 下载不需要认证
656
+ - `404` 表示目标 AID 尚未发布 `agent.md`
657
+ - SDK 在 `404` 时抛出 `NotFoundError`,其他非 2xx 状态抛出 `AUNError`
658
+
659
+ ```python
660
+ agent_md = await client.auth.download_agent_md("bob.agentid.pub")
661
+ ```
662
+
663
+ ---
664
+
665
+ ### `await renew_cert(params: dict | None) -> dict`
666
+
667
+ 续期当前 AID 的证书(保持相同密钥,只延长有效期)。透传到 `auth.renew_cert` RPC。
668
+
669
+ **使用场景**: 证书即将过期时的日常续期操作
670
+
671
+ **参数 / 返回值**: 详见协议文档 [01-身份与凭证协议-auth §1.6 auth.renew_cert](../../docs/protocol/01-身份与凭证协议-auth.md)
672
+
673
+ **注意**: 仅 Python SDK 提供此便捷方法;其他语言通过 `client.call("auth.renew_cert", params)` 调用
674
+
675
+ ---
676
+
677
+ ### `await rekey(params: dict | None) -> dict`
678
+
679
+ 重新生成密钥对并签发新证书(用于密钥泄露后的安全恢复)。透传到 `auth.rekey` RPC。
680
+
681
+ **使用场景**: 密钥泄露、安全事件响应、主动密钥轮换
682
+
683
+ **参数 / 返回值**: 详见协议文档 [01-身份与凭证协议-auth §1.6 auth.rekey](../../docs/protocol/01-身份与凭证协议-auth.md)
684
+
685
+ **注意**: 仅 Python SDK 提供此便捷方法;其他语言通过 `client.call("auth.rekey", params)` 调用
686
+
687
+ ---
688
+
689
+ ### `await request_cert(params: dict) -> dict`
690
+
691
+ 通用的证书请求接口。透传到 `auth.request_cert` RPC。
692
+
693
+ **使用场景**: 为已有 AID 申请不同曲线的额外证书
694
+
695
+ **参数 / 返回值**: 详见协议文档 [01-身份与凭证协议-auth §1.6 auth.request_cert](../../docs/protocol/01-身份与凭证协议-auth.md)
696
+
697
+ **注意**: 仅 Python SDK 提供此便捷方法;其他语言通过 `client.call("auth.request_cert", params)` 调用
698
+
699
+ ---
700
+
701
+ ## MetaNamespace (`client.meta`)
702
+
703
+ ### `await client.meta.ping(params: dict | None = None) -> Any`
704
+
705
+ 调用 `meta.ping` RPC,检测与网关的连通性。需已连接。
706
+
707
+ ### `await client.meta.status(params: dict | None = None) -> Any`
708
+
709
+ 调用 `meta.status` RPC,获取网关服务状态信息。需已连接。
710
+
711
+ ### `await client.meta.trust_roots(params: dict | None = None) -> Any`
712
+
713
+ 查询网关信任的 Root CA 列表(需已连接)。
714
+
715
+ **参数**: 无
716
+
717
+ **返回值**: 管理局签名的受信根证书列表。早期服务可能返回 `roots/count` 兼容结构。
718
+
719
+ ### `await client.meta.download_trust_roots(url: str | None = None, *, issuer: str | None = None, gateway_url: str | None = None, timeout: float = 10.0) -> dict`
720
+
721
+ 从管理局权威端点、`pki.{issuer}` 泛域名端点或 Gateway 镜像端点下载受信根列表。优先级为显式 `url`、`https://pki.{issuer}/trust-root.json`、已连接 Gateway 的 `https://gateway.{issuer}/pki/trust-roots.json`、管理局权威端点。
722
+
723
+ ### `await client.meta.download_issuer_root_cert(issuer: str, url: str | None = None, *, timeout: float = 10.0) -> str`
724
+
725
+ 从 `https://pki.{issuer}/root.crt` 下载该 issuer 证书链锚定的 Root CA PEM。该方法只下载和解析证书,不会导入本地信任根。
726
+
727
+ ### `client.meta.verify_trust_roots(trust_list: dict, *, authority_cert_pem: str | None = None, authority_public_key_pem: str | None = None, allow_unsigned: bool = False) -> dict`
728
+
729
+ 验证 `authority_signature`、`version`、`issued_at`、`next_update`、Root CA 证书有效期、CA 约束和 `fingerprint_sha256`,只返回可导入摘要,不写入本地信任根。默认拒绝未签名列表;`allow_unsigned=True` 仅用于私有测试环境。
730
+
731
+ ### `client.meta.import_trust_roots(trust_list: dict, *, authority_cert_pem: str | None = None, authority_public_key_pem: str | None = None, allow_unsigned: bool = False) -> dict`
732
+
733
+ 在 `verify_trust_roots()` 通过后,进一步检查 `version` 不低于本地已导入版本,再写入 `{aun_path}/CA/root/trust-roots.json` 和 `{aun_path}/CA/root/trust-roots.pem`,并刷新当前客户端的信任根缓存。
734
+
735
+ ### `await client.meta.refresh_trust_roots(...) -> dict`
736
+
737
+ 组合执行下载、验签、导入和刷新。应用层通常优先使用该方法。
738
+
739
+ ### `await client.meta.update_issuer_root_cert(issuer: str, *, cert_pem: str | None = None, url: str | None = None, trust_list: dict | None = None, authority_cert_pem: str | None = None, authority_public_key_pem: str | None = None, allow_unsigned: bool = False, timeout: float = 10.0) -> dict`
740
+
741
+ 下载或接收 `issuer` 的 `root.crt`,校验证书为自签 Root CA,并确认其 SHA-256 指纹存在于已验签的受信根列表中,通过后写入 `{aun_path}/CA/root/issuers/{issuer}.root.crt`,合并进 `{aun_path}/CA/root/trust-roots.pem`,并刷新当前客户端信任根缓存。
742
+
743
+ 顶层兼容方法 `await client.trust_roots()` 仍保留,等价于 `await client.meta.trust_roots()`。
744
+
745
+ ---
746
+
747
+ ## E2EEManager (`client.e2ee`)
748
+
749
+ > **高级 API**:主要供裸 WebSocket 开发者使用。普通 SDK 开发者无需额外操作——`call("message.send", ...)` 默认加密发送,SDK 会自动处理加密/解密,无需直接使用本节 API。
750
+ >
751
+ > `E2EEManager` 是纯密码学工具类,无 I/O 依赖,可独立于 `AUNClient` 实例化。
752
+ >
753
+ > 更详细的用法可参考 SDK 内部实现:`src/aun_core/client.py` 中 `_send_encrypted` / `_decrypt_message` 等方法。
754
+
755
+ ### 构造函数(裸 WebSocket 开发者使用)
756
+
757
+ ```python
758
+ E2EEManager(
759
+ *,
760
+ identity_fn, # () -> {aid, private_key_pem, public_key_der_b64}
761
+ keystore, # KeyStore protocol 实现
762
+ prekey_cache_ttl=3600.0, # prekey 缓存 TTL(秒)
763
+ )
764
+ ```
765
+
766
+ | 参数 | 类型 | 说明 |
767
+ |------|------|------|
768
+ | `identity_fn` | `() -> dict` | 返回当前身份信息 |
769
+ | `keystore` | `KeyStore` | 密钥存储实现 |
770
+ | `prekey_cache_ttl` | `float` | prekey 缓存过期时间,默认 3600 秒 |
771
+
772
+ ---
773
+
774
+ ### `encrypt_message(to_aid, payload, *, peer_cert_pem, prekey=None, message_id=None, timestamp=None) -> tuple[Any, bool]`
775
+
776
+ 加密消息(便利方法,自动生成 message_id / timestamp)。有 prekey → prekey_ecdh_v2,无 prekey → long_term_key。传入的 prekey 自动缓存。
777
+
778
+ **参数**
779
+
780
+ | 参数 | 类型 | 必填 | 说明 |
781
+ |------|------|------|------|
782
+ | `to_aid` | `str` | 是 | 对端 AID |
783
+ | `payload` | `dict` | 是 | 原始消息载荷 |
784
+ | `peer_cert_pem` | `bytes` | 是 | 对端证书(PEM) |
785
+ | `prekey` | `dict \| None` | 否 | 对端 prekey(None 时查缓存或降级) |
786
+ | `message_id` | `str` | 否 | 消息 ID(不传则自动生成) |
787
+ | `timestamp` | `int` | 否 | 时间戳毫秒(不传则自动生成) |
788
+
789
+ **返回值**: `(envelope, encrypted)` — 加密信封和是否成功标志
790
+
791
+ ---
792
+
793
+ ### `decrypt_message(message: dict) -> dict | None`
794
+
795
+ 解密单条消息,内置本地防重放(seen set)。重复消息返回 `None`。
796
+
797
+ **参数**
798
+
799
+ | 参数 | 类型 | 必填 | 说明 |
800
+ |------|------|------|------|
801
+ | `message` | `dict` | 是 | 原始消息 |
802
+
803
+ **返回值**: 解密后的消息,解密失败或重放返回 `None`
804
+
805
+ ---
806
+
807
+ ### `encrypt_outbound(peer_aid, payload, *, peer_cert_pem, prekey=None, message_id, timestamp) -> tuple[Any, bool]`
808
+
809
+ 加密出站消息(底层方法)。
810
+
811
+ **参数**
812
+
813
+ | 参数 | 类型 | 必填 | 说明 |
814
+ |------|------|------|------|
815
+ | `peer_aid` | `str` | 是 | 对端 AID |
816
+ | `payload` | `dict` | 是 | 原始消息载荷 |
817
+ | `peer_cert_pem` | `bytes` | 是 | 对端证书(PEM) |
818
+ | `prekey` | `dict \| None` | 否 | 对端 prekey |
819
+ | `message_id` | `str` | 是 | 消息 ID |
820
+ | `timestamp` | `int` | 是 | 时间戳(毫秒) |
821
+
822
+ **返回值**: `(envelope, encrypted)`
823
+
824
+ ---
825
+
826
+ ### `generate_prekey() -> dict`
827
+
828
+ 生成 prekey 材料(密钥对 + 签名),私钥保存在本地 keystore。返回上传材料,调用方自行上传到服务端。
829
+
830
+ **返回值**: `{"prekey_id": "uuid", "public_key": "base64", "signature": "base64"}`
831
+
832
+ ---
833
+
834
+ ### `cache_prekey(peer_aid, prekey) -> None`
835
+
836
+ 缓存对方的 prekey,后续 encrypt 自动复用。
837
+
838
+ ---
839
+
840
+ ### `get_cached_prekey(peer_aid) -> dict | None`
841
+
842
+ 获取缓存的 prekey,过期返回 `None`。
843
+
844
+ ---
845
+
846
+ ### `invalidate_prekey_cache(peer_aid) -> None`
847
+
848
+ 使指定 peer 的 prekey 缓存失效。
849
+
850
+ ---
851
+
852
+ ## GroupE2EEManager (`client.group_e2ee`)
853
+
854
+ > **高级 API**:主要供裸 WebSocket 开发者使用。普通 SDK 开发者无需额外操作——`call("group.send", ...)` 默认加密发送,SDK 自动处理群组加密/解密和密钥管理。
855
+ >
856
+ > `GroupE2EEManager` 是纯密码学 + 本地状态工具类,零 I/O 依赖,可独立于 `AUNClient` 实例化。
857
+ > 内置防重放、epoch 降级防护、密钥请求/响应频率限制。
858
+ >
859
+ > 更详细的用法可参考 SDK 内部实现:`src/aun_core/client.py` 中群组 E2EE 自动编排(`_rotate_group_epoch` / `_distribute_key_to_new_member` / `_try_handle_group_key_message` 等方法)。
860
+
861
+ ### 构造函数(群组 E2EE)
862
+
863
+ ```python
864
+ GroupE2EEManager(
865
+ *,
866
+ identity_fn, # () -> {aid, private_key_pem, ...}
867
+ keystore, # KeyStore protocol 实现
868
+ request_cooldown=30.0, # 密钥请求冷却时间(秒)
869
+ response_cooldown=30.0, # 密钥响应冷却时间(秒)
870
+ )
871
+ ```
872
+
873
+ | 参数 | 类型 | 说明 |
874
+ |------|------|------|
875
+ | `identity_fn` | `() -> dict` | 返回当前身份信息 |
876
+ | `keystore` | `KeyStore` | 密钥存储实现 |
877
+ | `request_cooldown` | `float` | 同一 group+epoch 密钥请求最小间隔,默认 30 秒 |
878
+ | `response_cooldown` | `float` | 同一 group+requester 密钥响应最小间隔,默认 30 秒 |
879
+
880
+ ---
881
+
882
+ ### `create_epoch(group_id, member_aids) -> dict`
883
+
884
+ 创建首个 epoch(建群时调用)。生成群密钥,本地存储,返回分发信息。
885
+
886
+ **参数**
887
+
888
+ | 参数 | 类型 | 必填 | 说明 |
889
+ |------|------|------|------|
890
+ | `group_id` | `str` | 是 | 群组 ID |
891
+ | `member_aids` | `list[str]` | 是 | 初始成员 AID 列表 |
892
+
893
+ **返回值**: `{epoch: 1, commitment: str, distributions: [{to: str, payload: dict}]}`
894
+
895
+ 调用方需将 `distributions` 中的每个 payload 通过 P2P E2EE 发送给对应成员。
896
+
897
+ ---
898
+
899
+ ### `rotate_epoch(group_id, member_aids) -> dict`
900
+
901
+ 轮换 epoch(踢人/定时轮换时调用)。自动递增 epoch 号,返回格式与 `create_epoch` 相同。
902
+
903
+ **参数**
904
+
905
+ | 参数 | 类型 | 必填 | 说明 |
906
+ |------|------|------|------|
907
+ | `group_id` | `str` | 是 | 群组 ID |
908
+ | `member_aids` | `list[str]` | 是 | 轮换后的成员列表(不含被踢成员) |
909
+
910
+ **返回值**: `{epoch, commitment, distributions}`
911
+
912
+ ---
913
+
914
+ ### `rotate_epoch_to(group_id, target_epoch, member_aids) -> dict`
915
+
916
+ 指定目标 epoch 号轮换(配合服务端 CAS 使用)。当服务端通过 CAS 分配了 epoch 号后,用此方法生成对应密钥。
917
+
918
+ **参数**
919
+
920
+ | 参数 | 类型 | 必填 | 说明 |
921
+ |------|------|------|------|
922
+ | `group_id` | `str` | 是 | 群组 ID |
923
+ | `target_epoch` | `int` | 是 | 服务端 CAS 分配的 epoch 号 |
924
+ | `member_aids` | `list[str]` | 是 | 成员列表 |
925
+
926
+ **返回值**: `{epoch, commitment, distributions}`
927
+
928
+ ---
929
+
930
+ ### `encrypt(group_id, payload, *, message_id=None, timestamp=None) -> dict`
931
+
932
+ 加密群消息。使用当前 epoch 的群密钥加密。无密钥时抛 `E2EEGroupSecretMissingError`。
933
+
934
+ **参数**
935
+
936
+ | 参数 | 类型 | 必填 | 说明 |
937
+ |------|------|------|------|
938
+ | `group_id` | `str` | 是 | 群组 ID |
939
+ | `payload` | `dict` | 是 | 原始消息载荷 |
940
+ | `message_id` | `str` | 否 | 消息 ID(不传则自动生成) |
941
+ | `timestamp` | `int` | 否 | 时间戳毫秒(不传则自动生成) |
942
+
943
+ **返回值**: 加密信封 `dict`(`type: "e2ee.group_encrypted"`)
944
+
945
+ ---
946
+
947
+ ### `decrypt(message: dict) -> dict | None`
948
+
949
+ 解密单条群消息。内置防重放 + 外层 `group_id` / `from` / `sender_aid` 校验。非加密消息原样返回,解密失败返回 `None`。
950
+
951
+ **参数**
952
+
953
+ | 参数 | 类型 | 必填 | 说明 |
954
+ |------|------|------|------|
955
+ | `message` | `dict` | 是 | 原始群消息(含 payload、group_id、from 等字段) |
956
+
957
+ **返回值**: 解密后的消息,解密失败返回 `None`,非加密消息原样返回
958
+
959
+ ---
960
+
961
+ ### `decrypt_batch(messages) -> list`
962
+
963
+ 批量解密群消息(用于 `group.pull` 返回的消息列表)。解密失败的消息保留原始内容。
964
+
965
+ ---
966
+
967
+ ### `handle_incoming(payload: dict) -> str | None`
968
+
969
+ 处理已解密的 P2P 密钥消息(分发/请求/响应)。收到 P2P 消息后先解密,再将内层 payload 传入此方法。
970
+
971
+ **返回值**:
972
+
973
+ | 返回值 | 含义 |
974
+ |--------|------|
975
+ | `"distribution"` | 密钥分发已存储 |
976
+ | `"distribution_rejected"` | epoch 降级被拒 |
977
+ | `"request"` | 收到密钥请求,需调用 `handle_key_request_msg` 构建响应 |
978
+ | `"response"` | 密钥恢复响应已存储 |
979
+ | `"response_rejected"` | 响应被拒(epoch 降级) |
980
+ | `None` | 不是密钥消息 |
981
+
982
+ ---
983
+
984
+ ### `build_recovery_request(group_id, epoch, *, sender_aid=None) -> dict | None`
985
+
986
+ 构建密钥恢复请求(缺密钥时调用)。受频率限制,冷却期内返回 `None`。
987
+
988
+ **参数**
989
+
990
+ | 参数 | 类型 | 必填 | 说明 |
991
+ |------|------|------|------|
992
+ | `group_id` | `str` | 是 | 群组 ID |
993
+ | `epoch` | `int` | 是 | 需要恢复的 epoch |
994
+ | `sender_aid` | `str` | 否 | 消息发送者 AID(备选恢复目标) |
995
+
996
+ **返回值**: `{to: str, payload: dict}` 或 `None`(限流/无目标时)
997
+
998
+ 调用方需将 `payload` 通过 P2P E2EE 发送给 `to`。
999
+
1000
+ ---
1001
+
1002
+ ### `handle_key_request_msg(request_payload, current_members) -> dict | None`
1003
+
1004
+ 处理密钥请求并构建响应(受频率限制)。校验请求者是否在 `current_members` 中。
1005
+
1006
+ **参数**
1007
+
1008
+ | 参数 | 类型 | 必填 | 说明 |
1009
+ |------|------|------|------|
1010
+ | `request_payload` | `dict` | 是 | 密钥请求消息(含 requester_aid、group_id、epoch) |
1011
+ | `current_members` | `list[str]` | 是 | 当前群成员列表(用于校验请求者身份) |
1012
+
1013
+ **返回值**: 响应 payload `dict`,或 `None`(非成员/限流/无密钥时)
1014
+
1015
+ > **注意**:SDK 自动编排中,如果请求者不在本地 `member_aids` 中,会先回源查询 `group.get_members` 获取服务端最新成员列表后再调用此方法。裸 WebSocket 开发者也应实现类似逻辑。
1016
+
1017
+ ---
1018
+
1019
+ ### `has_secret(group_id) -> bool`
1020
+
1021
+ 查询指定群组是否有本地密钥。
1022
+
1023
+ ---
1024
+
1025
+ ### `current_epoch(group_id) -> int | None`
1026
+
1027
+ 获取指定群组的当前 epoch 号,无密钥时返回 `None`。
1028
+
1029
+ ---
1030
+
1031
+ ### `get_member_aids(group_id) -> list`
1032
+
1033
+ 获取指定群组当前 epoch 的本地成员列表,无密钥时返回空列表。
1034
+
1035
+ > **注意**:返回的是本地保存的成员视图,不一定与服务端最新一致。需要最新列表时应查询 `group.get_members`。
1036
+
1037
+ ---
1038
+
1039
+ ## Subscription
1040
+
1041
+ `client.on()` 的返回值。
1042
+
1043
+ ### `unsubscribe() -> None`
1044
+
1045
+ 取消事件订阅,幂等(多次调用无副作用)。
1046
+
1047
+ ---
1048
+
1049
+ ## 内置事件
1050
+
1051
+ | 事件名 | 触发时机 | payload 结构 |
1052
+ |--------|----------|--------------|
1053
+ | `message.received` | 收到新消息推送 | `Message` 对象 |
1054
+ | `message.recalled` | 消息被撤回 | 撤回信息 |
1055
+ | `message.ack` | 消息已读确认 | `{"ack_seq": N, "device_id": "...", "slot_id": "..."}` |
1056
+ | `message.undecryptable` | P2P 消息解密失败 | 原始加密消息 |
1057
+ | `group.changed` | 群组状态变更 | 变更详情 |
1058
+ | `group.message_created` | 收到群消息推送 | 群消息对象 |
1059
+ | `group.message_undecryptable` | 群消息解密失败 | 原始加密群消息 |
1060
+ | `storage.object_changed` | 存储对象变更(put/delete) | `{"action": "put"/"delete", "owner_aid": "...", "object_key": "..."}` |
1061
+ | `e2ee.degraded` | E2EE 降级为 long_term_key | `{"peer_aid": "...", "reason": "..."}` |
1062
+ | `e2ee.orchestration_error` | 群 E2EE 编排失败 | 错误详情 |
1063
+ | `connection.state` | 连接状态变化 | `{"state": "..."}` |
1064
+ | `connection.challenge` | 收到认证挑战 | 挑战参数 |
1065
+ | `connection.error` | 连接发生错误 | 异常信息 |
1066
+ | `token.refreshed` | 访问令牌已刷新 | `{"aid": "..."}` |
1067
+ | `token.refresh_exhausted` | Token 刷新重试耗尽 | `{"aid": "...", "consecutive_failures": N}` |
1068
+ | `notification` | 未分类推送通知 | 原始消息体 |
1069
+
1070
+ ---
1071
+
1072
+ ## RPC 方法参考
1073
+
1074
+ 所有业务操作通过 `client.call(method, params)` 调用,参数和返回值详见 RPC 手册:
1075
+
1076
+ | 领域 | 手册 | 涵盖方法 |
1077
+ |------|------|----------|
1078
+ | 消息 | [message/04-RPC-Manual.md](../src/aun_core/docs/skill/rpc-manual/message/04-RPC-Manual.md) | message.send / pull / ack / recall |
1079
+ | 群组 | [group/04-RPC-Manual.md](../src/aun_core/docs/skill/rpc-manual/group/04-RPC-Manual.md) | 群组生命周期、成员管理、群消息 |
1080
+ | 存储 | [storage/04-RPC-Manual.md](../src/aun_core/docs/skill/rpc-manual/storage/04-RPC-Manual.md) | 文件上传下载、对象存储 |
1081
+ | 流 | [stream/04-RPC-Manual.md](../src/aun_core/docs/skill/rpc-manual/stream/04-RPC-Manual.md) | stream.create / close / get_info / list_active |
1082
+ | 元信息 | [meta/01-RPC-Manual.md](../src/aun_core/docs/skill/rpc-manual/meta/01-RPC-Manual.md) | meta.ping / status / trust_roots |
1083
+
1084
+ 可运行示例见 [examples/](../src/aun_core/docs/skill/examples/)。
1085
+
1086
+ ---
1087
+
1088
+ ## Stream 使用指南
1089
+
1090
+ Stream 服务用于实时流式数据传输(LLM 输出、数据推送等)。控制面通过 `client.call()` 管理,数据面通过原生 WebSocket / HTTP SSE 传输。
1091
+
1092
+ > 详细协议规范见 [12-Stream-子协议](../src/aun_core/docs/protocol/12-Stream-子协议.md)
1093
+
1094
+ ### 创建流
1095
+
1096
+ ```python
1097
+ result = await client.call("stream.create", {
1098
+ "content_type": "text/plain", # 可选,默认 text/plain
1099
+ "metadata": {"model": "gpt-4"}, # 可选,自定义元数据
1100
+ "target_aid": "bob.aid.net", # 可选,仅在拉流方显式提供 aid 时做匹配校验
1101
+ })
1102
+ stream_id = result["stream_id"]
1103
+ push_url = result["push_url"] # WebSocket 推流地址
1104
+ pull_url = result["pull_url"] # HTTP SSE 拉流地址
1105
+ push_token = result["push_token"] # 推流凭证
1106
+ pull_token = result["pull_token"] # 拉流凭证
1107
+ push_headers = result["push_headers"] # 推荐使用 Authorization header
1108
+ pull_headers = result["pull_headers"] # 推荐使用 Authorization header
1109
+ ```
1110
+
1111
+ > 当前实现仍保留 URL query token 以兼容旧客户端;新客户端优先使用 `push_headers` / `pull_headers`。
1112
+
1113
+ ### 推流(WebSocket)
1114
+
1115
+ ```python
1116
+ import websockets, json
1117
+
1118
+ async with websockets.connect(
1119
+ push_url,
1120
+ ssl=ssl_ctx,
1121
+ additional_headers=push_headers,
1122
+ ) as ws:
1123
+ await ws.send(json.dumps({"cmd": "data", "data": "Hello ", "seq": 1}))
1124
+ await ws.send(json.dumps({"cmd": "data", "data": "World", "seq": 2}))
1125
+ await ws.send(json.dumps({"cmd": "close"}))
1126
+ ```
1127
+
1128
+ ### 拉流(HTTP SSE)
1129
+
1130
+ ```python
1131
+ import aiohttp
1132
+
1133
+ async with aiohttp.ClientSession() as session:
1134
+ async with session.get(pull_url, headers=pull_headers) as resp:
1135
+ async for line in resp.content:
1136
+ # SSE 格式:id: {seq}\ndata: {内容}\n\n
1137
+ pass
1138
+
1139
+ > 推流连接若失败,当前实现常见返回为 HTTP `403` / `404` / `410`,应优先检查升级前的 HTTP 状态码。
1140
+ ```
1141
+
1142
+ ### 关闭流
1143
+
1144
+ ```python
1145
+ await client.call("stream.close", {"stream_id": stream_id})
1146
+ ```
1147
+
1148
+ ### 查询流状态
1149
+
1150
+ ```python
1151
+ info = await client.call("stream.get_info", {"stream_id": stream_id})
1152
+ streams = await client.call("stream.list_active", {})