@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.
- package/CHANGELOG.md +26 -0
- package/_packed_docs/CHANGELOG.md +26 -0
- package/_packed_docs/agent.md/SCHEMA.md +173 -0
- package/_packed_docs/agent.md/examples/codeagent-claudecode.md +61 -0
- package/_packed_docs/agent.md/examples/human-developer.md +60 -0
- package/_packed_docs/agent.md/examples/openclaw-lobster.md +52 -0
- package/_packed_docs/agent.md/examples/signed-openclaw-lobster.md +43 -0
- package/_packed_docs/protocol/00-/346/200/273/350/247/210/344/270/216/345/210/206/345/261/202.md +205 -0
- 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
- 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
- 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
- package/_packed_docs/protocol/03-Gateway-/350/277/236/346/216/245/346/250/241/345/274/217.md +262 -0
- package/_packed_docs/protocol/04-Peer-/345/255/220/345/215/217/350/256/256.md +180 -0
- package/_packed_docs/protocol/05-Relay-/345/255/220/345/215/217/350/256/256.md +164 -0
- package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +1135 -0
- 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
- package/_packed_docs/protocol/08-AUN-E2EE-Group.md +900 -0
- package/_packed_docs/protocol/08-AUN-E2EE.md +413 -0
- package/_packed_docs/protocol/09-/345/256/211/345/205/250/350/200/203/350/231/221.md +316 -0
- package/_packed_docs/protocol/10-Group-/345/255/220/345/215/217/350/256/256.md +804 -0
- package/_packed_docs/protocol/11-Storage-/345/255/220/345/215/217/350/256/256.md +271 -0
- package/_packed_docs/protocol/12-Stream-/345/255/220/345/215/217/350/256/256.md +329 -0
- package/_packed_docs/protocol/13-Agent/350/241/214/344/270/272/350/247/204/350/214/203.md +141 -0
- 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
- package/_packed_docs/protocol/README.md +71 -0
- package/_packed_docs/protocol/agent.md/SCHEMA.md +118 -0
- package/_packed_docs/protocol/agent.md/examples/codeagent-claudecode.md +61 -0
- package/_packed_docs/protocol/agent.md/examples/human-developer.md +60 -0
- package/_packed_docs/protocol/agent.md/examples/openclaw-lobster.md +52 -0
- package/_packed_docs/protocol/aun-docs-guide.md +49 -0
- package/_packed_docs/protocol/index.md +114 -0
- 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
- 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
- package/_packed_docs/protocol//351/231/204/345/275/225A-/346/234/257/350/257/255/350/241/250.md +337 -0
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- package/_packed_docs/sdk/01-/345/277/253/351/200/237/345/274/200/345/247/213.md +223 -0
- package/_packed_docs/sdk/02-WebSocket/345/215/217/350/256/256.md +354 -0
- package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +172 -0
- package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +373 -0
- package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +611 -0
- package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +1152 -0
- package/_packed_docs/sdk/07-/351/224/231/350/257/257/345/244/204/347/220/206.md +150 -0
- package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +89 -0
- package/_packed_docs/sdk/09-custody-api-manual.md +445 -0
- package/_packed_docs/sdk/09-group-rpc-manual.md +1895 -0
- package/_packed_docs/sdk/09-message-rpc-manual.md +597 -0
- package/_packed_docs/sdk/09-meta-rpc-manual.md +142 -0
- package/_packed_docs/sdk/09-payload-reference.md +702 -0
- package/_packed_docs/sdk/09-storage-rpc-manual.md +408 -0
- package/_packed_docs/sdk/09-stream-rpc-manual.md +275 -0
- package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +72 -0
- package/_packed_docs/sdk/INDEX.md +131 -0
- package/_packed_docs/sdk/README.md +307 -0
- package/dist/auth.d.ts +2 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +13 -11
- package/dist/auth.js.map +1 -1
- package/dist/client.d.ts +38 -8
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +179 -97
- package/dist/client.js.map +1 -1
- package/dist/namespaces/auth.d.ts +1 -0
- package/dist/namespaces/auth.d.ts.map +1 -1
- package/dist/namespaces/auth.js +20 -6
- package/dist/namespaces/auth.js.map +1 -1
- package/dist/transport.d.ts +9 -1
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +24 -0
- package/dist/transport.js.map +1 -1
- package/package.json +40 -37
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# AUN SDK Python - 快速开始
|
|
2
|
+
|
|
3
|
+
**版本**: 0.2.8 | **Python**: >= 3.11
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 安装
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install fastaun
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 最小示例
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
import asyncio, random
|
|
19
|
+
from datetime import datetime
|
|
20
|
+
from aun_core import AUNClient
|
|
21
|
+
from aun_core.errors import AUNError, ConnectionError, AuthError
|
|
22
|
+
|
|
23
|
+
def ts():
|
|
24
|
+
return datetime.now().strftime("%H:%M:%S.%f")[:-3]
|
|
25
|
+
|
|
26
|
+
# ── 配置(按需修改)──
|
|
27
|
+
DOMAIN = "agentid.pub" # 也支持其它 AP,如 agentcp.io
|
|
28
|
+
ALICE = f"alice{random.randint(1000,9999)}.{DOMAIN}"
|
|
29
|
+
BOB = f"bob{random.randint(1000,9999)}.{DOMAIN}"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
async def create_client(aid: str) -> tuple[AUNClient, dict]:
|
|
33
|
+
"""创建客户端 → 加载或创建 AID → 认证 → 返回 (client, auth)"""
|
|
34
|
+
client = AUNClient() # 默认 aun_path: ~/.aun
|
|
35
|
+
|
|
36
|
+
# 先检查本地是否已有该 AID 的身份
|
|
37
|
+
known = any(item["aid"] == aid for item in client.list_identities())
|
|
38
|
+
if not known:
|
|
39
|
+
# 不存在则创建
|
|
40
|
+
try:
|
|
41
|
+
await client.auth.create_aid({"aid": aid})
|
|
42
|
+
except AuthError as e:
|
|
43
|
+
print(f"[错误] 创建 AID 失败 ({aid}): {e}")
|
|
44
|
+
raise
|
|
45
|
+
except ConnectionError as e:
|
|
46
|
+
print(f"[错误] 网络连接失败: {e}")
|
|
47
|
+
raise
|
|
48
|
+
except Exception as e:
|
|
49
|
+
print(f"[错误] 未知错误: {e}")
|
|
50
|
+
raise
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
auth = await client.auth.authenticate({"aid": aid})
|
|
54
|
+
return client, auth
|
|
55
|
+
except AuthError as e:
|
|
56
|
+
print(f"[错误] 认证失败 ({aid}): {e}")
|
|
57
|
+
raise
|
|
58
|
+
except ConnectionError as e:
|
|
59
|
+
print(f"[错误] 网络连接失败: {e}")
|
|
60
|
+
raise
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
async def main():
|
|
64
|
+
alice = None
|
|
65
|
+
bob = None
|
|
66
|
+
|
|
67
|
+
try:
|
|
68
|
+
# 1. 创建两个客户端
|
|
69
|
+
alice, alice_auth = await create_client(ALICE)
|
|
70
|
+
bob, bob_auth = await create_client(BOB)
|
|
71
|
+
|
|
72
|
+
# 2. Bob 订阅消息事件
|
|
73
|
+
received = asyncio.Event()
|
|
74
|
+
def on_bob_message(event):
|
|
75
|
+
print(f"[{ts()}] [Bob 收到] {event['payload']}")
|
|
76
|
+
received.set()
|
|
77
|
+
|
|
78
|
+
bob.on("message.received", on_bob_message)
|
|
79
|
+
|
|
80
|
+
# 3. 双方连接到网关
|
|
81
|
+
try:
|
|
82
|
+
await alice.connect(alice_auth, {})
|
|
83
|
+
await bob.connect(bob_auth, {})
|
|
84
|
+
print(f"[{ts()}] Alice ({ALICE}) 已连接")
|
|
85
|
+
print(f"[{ts()}] Bob ({BOB}) 已连接")
|
|
86
|
+
except ConnectionError as e:
|
|
87
|
+
print(f"[错误] 连接网关失败: {e}")
|
|
88
|
+
raise
|
|
89
|
+
|
|
90
|
+
# 4. Alice 发消息给 Bob
|
|
91
|
+
try:
|
|
92
|
+
result = await alice.call("message.send", {
|
|
93
|
+
"to": BOB,
|
|
94
|
+
"payload": {"type": "text", "text": "Hello from Alice!"},
|
|
95
|
+
})
|
|
96
|
+
print(f"[{ts()}] [Alice 发送] {result}")
|
|
97
|
+
except AUNError as e:
|
|
98
|
+
print(f"[错误] 发送消息失败: {e}")
|
|
99
|
+
raise
|
|
100
|
+
|
|
101
|
+
# 5. 等待 Bob 收到消息(最多 5 秒)
|
|
102
|
+
try:
|
|
103
|
+
await asyncio.wait_for(received.wait(), timeout=5.0)
|
|
104
|
+
except asyncio.TimeoutError:
|
|
105
|
+
# 事件推送未触发,尝试主动拉取
|
|
106
|
+
try:
|
|
107
|
+
pull = await bob.call("message.pull", {"after_seq": 0, "limit": 10})
|
|
108
|
+
msgs = pull.get("messages", [])
|
|
109
|
+
if msgs:
|
|
110
|
+
print(f"[{ts()}] [Bob 拉取] 收到 {len(msgs)} 条消息:")
|
|
111
|
+
for m in msgs:
|
|
112
|
+
print(f" {m.get('payload')}")
|
|
113
|
+
else:
|
|
114
|
+
print(f"[{ts()}] [Bob] 未收到消息")
|
|
115
|
+
except AUNError as e:
|
|
116
|
+
print(f"[错误] 拉取消息失败: {e}")
|
|
117
|
+
|
|
118
|
+
print(f"[{ts()}] 完成")
|
|
119
|
+
|
|
120
|
+
except KeyboardInterrupt:
|
|
121
|
+
print(f"\n[{ts()}] 用户中断")
|
|
122
|
+
except Exception as e:
|
|
123
|
+
print(f"[{ts()}] 程序异常: {e}")
|
|
124
|
+
raise
|
|
125
|
+
finally:
|
|
126
|
+
# 6. 关闭连接
|
|
127
|
+
if alice:
|
|
128
|
+
try:
|
|
129
|
+
await alice.close()
|
|
130
|
+
except Exception:
|
|
131
|
+
pass
|
|
132
|
+
if bob:
|
|
133
|
+
try:
|
|
134
|
+
await bob.close()
|
|
135
|
+
except Exception:
|
|
136
|
+
pass
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
asyncio.run(main())
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## 配置
|
|
145
|
+
|
|
146
|
+
### 构造参数
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
client = AUNClient({
|
|
150
|
+
"aun_path": "~/.aun/myapp", # 应用级数据目录(AID 数据在其下的 AIDs/{aid}/ 中)
|
|
151
|
+
"root_ca_path": "/path/to/ca.pem", # 额外 Root CA(可选)
|
|
152
|
+
"seed_password": "seed", # 本地存储保护口令(可选)
|
|
153
|
+
})
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
`verify_ssl` 不在构造参数中配置。Python / TS / Go SDK 会根据环境变量自动决定:
|
|
157
|
+
|
|
158
|
+
- `AUN_ENV` 优先,其次 `KITE_ENV`
|
|
159
|
+
- 值为 `development` / `dev` / `local` 时关闭证书校验
|
|
160
|
+
- 其他值或未配置时开启证书校验
|
|
161
|
+
|
|
162
|
+
> **默认行为**:SDK 默认启用 P2P E2EE;Group E2EE 为必选能力并固定启用。`message.send` 和 `group.send` 默认加密发送,`group.thought.put` 强制加密发送;如需发送明文普通消息,显式传入 `encrypt=False`。`slot_id` 和 `delivery_mode` 只在 `connect(...)` 阶段传入。
|
|
163
|
+
|
|
164
|
+
完整参数列表见 [API 手册](06-API手册.md)。
|
|
165
|
+
|
|
166
|
+
### 数据目录布局
|
|
167
|
+
|
|
168
|
+
SDK 使用 `{aun_path}/AIDs/{aid}/` 存储每个 AID 的专属数据:
|
|
169
|
+
|
|
170
|
+
```
|
|
171
|
+
{aun_path}/ # 应用级数据根目录
|
|
172
|
+
├── .device_id # 设备级稳定标识(默认写在 ~/.aun/.device_id)
|
|
173
|
+
└── AIDs/ # AID 数据根
|
|
174
|
+
├── alice.agentid.pub/ # Alice 的全部数据
|
|
175
|
+
│ ├── private/key.json # ECDSA 私钥(加密保护)
|
|
176
|
+
│ ├── public/cert.pem # X.509 证书
|
|
177
|
+
│ └── tokens/meta.json # 令牌 + E2EE 密钥 + 元数据
|
|
178
|
+
└── bob.agentid.pub/ # Bob 的全部数据(多 AID 共存)
|
|
179
|
+
├── private/key.json
|
|
180
|
+
├── public/cert.pem
|
|
181
|
+
└── tokens/meta.json
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
**路径模型要点**:
|
|
185
|
+
|
|
186
|
+
- **`aun_path`** 是应用级目录,不是 AID 级目录。一个 `aun_path` 下可管理多个 AID
|
|
187
|
+
- **`aun_path` 不要用 AID 命名**(如 `~/.aun/{aid}`),否则会产生 `~/.aun/{aid}/AIDs/{aid}/` 的冗余嵌套
|
|
188
|
+
- 默认路径为 `~/.aun`
|
|
189
|
+
- 推荐用应用名或用途命名:`~/.aun/myapp`、`~/.aun/testing`
|
|
190
|
+
|
|
191
|
+
### connect 阶段的多实例参数
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
await client.connect(auth, {
|
|
195
|
+
"slot_id": "slot-a",
|
|
196
|
+
"delivery_mode": {
|
|
197
|
+
"mode": "queue",
|
|
198
|
+
"routing": "sender_affinity",
|
|
199
|
+
"affinity_ttl_ms": 300000,
|
|
200
|
+
},
|
|
201
|
+
})
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
- `slot_id` 由应用层传入;空字符串表示该设备只运行一个实例
|
|
205
|
+
- `delivery_mode` 只在 `connect(...)` 传入;同一 AID 的所有在线实例必须保持一致
|
|
206
|
+
|
|
207
|
+
**敏感数据保护**:`private/key.json` 和 `tokens/meta.json` 中的私钥、令牌、群密钥等敏感字段不以明文存储,而是通过平台密钥链(Windows DPAPI / macOS Keychain / Linux libsecret)加密保护。
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## 核心流程
|
|
212
|
+
|
|
213
|
+
完整的使用流程见上方"最小示例",核心步骤:
|
|
214
|
+
|
|
215
|
+
1. **创建客户端** - `AUNClient(config)`
|
|
216
|
+
2. **加载或创建 AID** - `load_identity_or_none()` → `create_aid()`
|
|
217
|
+
3. **认证** - `authenticate()` 获取令牌
|
|
218
|
+
4. **订阅事件** - `on("message.received", handler)`
|
|
219
|
+
5. **连接** - `connect(auth, options)`
|
|
220
|
+
6. **业务操作** - `call("message.send", params)`
|
|
221
|
+
7. **关闭** - `close()`
|
|
222
|
+
|
|
223
|
+
详细 API 说明见 [06-API手册.md](06-API手册.md),RPC 方法参数见各领域的 RPC 手册。
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
# AUN SDK Python - WebSocket 协议
|
|
2
|
+
|
|
3
|
+
本章介绍 AUN Gateway 的 WebSocket 协议细节。掌握这些内容后,可以用任何语言(或直接用 `websockets` 库)实现客户端,不依赖 SDK 的高层 API。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 连接握手
|
|
8
|
+
|
|
9
|
+
```mermaid
|
|
10
|
+
sequenceDiagram
|
|
11
|
+
participant C as 客户端
|
|
12
|
+
participant G as Gateway
|
|
13
|
+
|
|
14
|
+
C->>G: WebSocket 连接
|
|
15
|
+
G->>C: challenge(nonce, protocol, auth_methods)
|
|
16
|
+
C->>G: auth.connect(nonce, token, protocol, device, client, delivery_mode, options, capabilities)
|
|
17
|
+
G->>C: hello-ok(identity, capabilities)
|
|
18
|
+
Note over C,G: 握手完成,进入双向 RPC / 事件通信
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### (1) challenge — 服务器发送挑战
|
|
22
|
+
|
|
23
|
+
连接建立后,Gateway 立即发送:
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"jsonrpc": "2.0",
|
|
28
|
+
"method": "challenge",
|
|
29
|
+
"params": {
|
|
30
|
+
"nonce": "随机字符串(防重放)",
|
|
31
|
+
"server": "kite-gateway",
|
|
32
|
+
"protocol": { "min": "1.0", "max": "1.0" },
|
|
33
|
+
"auth_methods": ["pairing_code", "kite_token", "aid"],
|
|
34
|
+
"capabilities": { ... },
|
|
35
|
+
"server_time": 1774878000.123
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### (2) auth.connect — 客户端认证
|
|
41
|
+
|
|
42
|
+
客户端回复,携带 token 和从 challenge 中获取的 nonce:
|
|
43
|
+
|
|
44
|
+
```json
|
|
45
|
+
{
|
|
46
|
+
"jsonrpc": "2.0",
|
|
47
|
+
"id": "rpc-xxxx",
|
|
48
|
+
"method": "auth.connect",
|
|
49
|
+
"params": {
|
|
50
|
+
"nonce": "从 challenge 中获取",
|
|
51
|
+
"auth": {
|
|
52
|
+
"method": "kite_token",
|
|
53
|
+
"token": "authenticate() 返回的 access_token"
|
|
54
|
+
},
|
|
55
|
+
"protocol": { "min": "1.0", "max": "1.0" },
|
|
56
|
+
"device": { "id": "来自 ~/.aun/.device_id", "type": "desktop" },
|
|
57
|
+
"client": { "slot_id": "slot-a" },
|
|
58
|
+
"delivery_mode": {
|
|
59
|
+
"mode": "queue",
|
|
60
|
+
"routing": "sender_affinity",
|
|
61
|
+
"affinity_ttl_ms": 300000
|
|
62
|
+
},
|
|
63
|
+
"options": { "kind": "long" },
|
|
64
|
+
"capabilities": {
|
|
65
|
+
"e2ee": true,
|
|
66
|
+
"group_e2ee": true
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
说明:
|
|
73
|
+
|
|
74
|
+
- `protocol.min/max` 在 `auth.connect` 阶段完成 Gateway 会话版本协商;详细规则见协议文档 `03-Gateway-连接模式.md`。
|
|
75
|
+
- `device.id` 是设备级稳定标识,Python SDK 默认从 `~/.aun/.device_id` 读取。
|
|
76
|
+
- `client.slot_id` 由应用层显式传入,用于区分同设备上的多个实例槽位。
|
|
77
|
+
- `delivery_mode` 决定该 AID 当前连接的投递语义;同一 AID 的所有在线连接必须保持一致。
|
|
78
|
+
- `options.kind` 声明连接类型:`"long"`(默认)= 长连接,承担服务端推送 / 事件订阅;`"short"` = 短连接,仅用于发送 RPC 并等待响应即断开。同 `(aid, device.id, client.slot_id)` 槽位下,长连接最多 1 条,短连接最多 10 条;短连接不会顶掉长连接。
|
|
79
|
+
- `options.short_ttl_ms` 仅在 `kind="short"` 时有效,可选;服务端兜底超时后主动关闭短连接,防止占名额。
|
|
80
|
+
- `capabilities` 是客户端能力声明;`hello-ok.result.capabilities` 是服务端能力公告,不是双方能力交集。
|
|
81
|
+
|
|
82
|
+
### (3) hello-ok — 握手完成
|
|
83
|
+
|
|
84
|
+
认证成功后 Gateway 返回:
|
|
85
|
+
|
|
86
|
+
```json
|
|
87
|
+
{
|
|
88
|
+
"jsonrpc": "2.0",
|
|
89
|
+
"id": "rpc-xxxx",
|
|
90
|
+
"result": {
|
|
91
|
+
"status": "ok",
|
|
92
|
+
"protocol": "1.0",
|
|
93
|
+
"server_time": 1774878000.456,
|
|
94
|
+
"authenticated": true,
|
|
95
|
+
"identity": {
|
|
96
|
+
"module_id": "gateway-client-xxxx",
|
|
97
|
+
"role": "agent",
|
|
98
|
+
"aid": "alice1234.agentid.pub"
|
|
99
|
+
},
|
|
100
|
+
"connection": {
|
|
101
|
+
"id": "conn_gateway-client-xxxx",
|
|
102
|
+
"device_id": "dev-001",
|
|
103
|
+
"kind": "long"
|
|
104
|
+
},
|
|
105
|
+
"capabilities": { ... }
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## 消息格式
|
|
113
|
+
|
|
114
|
+
所有消息遵循 JSON-RPC 2.0 格式,通过以下规则区分类型:
|
|
115
|
+
|
|
116
|
+
| 类型 | 特征 | 示例 |
|
|
117
|
+
|------|------|------|
|
|
118
|
+
| **RPC 请求** | 有 `id` + `method` | `{"id": "rpc-1", "method": "message.send", "params": {...}}` |
|
|
119
|
+
| **RPC 响应** | 有 `id` + `result`/`error` | `{"id": "rpc-1", "result": {...}}` |
|
|
120
|
+
| **事件通知** | 无 `id`,`method` 以 `event/` 开头 | `{"method": "event/message.received", "params": {...}}` |
|
|
121
|
+
|
|
122
|
+
### RPC 请求
|
|
123
|
+
|
|
124
|
+
```json
|
|
125
|
+
{
|
|
126
|
+
"jsonrpc": "2.0",
|
|
127
|
+
"id": "rpc-随机hex",
|
|
128
|
+
"method": "message.send",
|
|
129
|
+
"params": {
|
|
130
|
+
"to": "bob.agentid.pub",
|
|
131
|
+
"payload": {"type": "text", "text": "Hello"}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### RPC 响应
|
|
137
|
+
|
|
138
|
+
成功:
|
|
139
|
+
```json
|
|
140
|
+
{
|
|
141
|
+
"jsonrpc": "2.0",
|
|
142
|
+
"id": "rpc-随机hex",
|
|
143
|
+
"result": {
|
|
144
|
+
"message_id": "uuid",
|
|
145
|
+
"seq": 1,
|
|
146
|
+
"status": "delivered"
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
错误:
|
|
152
|
+
```json
|
|
153
|
+
{
|
|
154
|
+
"jsonrpc": "2.0",
|
|
155
|
+
"id": "rpc-随机hex",
|
|
156
|
+
"error": {
|
|
157
|
+
"code": -32603,
|
|
158
|
+
"message": "错误描述"
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### 事件通知
|
|
164
|
+
|
|
165
|
+
服务器推送,无 `id` 字段:
|
|
166
|
+
|
|
167
|
+
```json
|
|
168
|
+
{
|
|
169
|
+
"jsonrpc": "2.0",
|
|
170
|
+
"method": "event/message.received",
|
|
171
|
+
"params": {
|
|
172
|
+
"from": "alice.agentid.pub",
|
|
173
|
+
"to": "bob.agentid.pub",
|
|
174
|
+
"payload": {"type": "text", "text": "Hello"}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## 完整示例
|
|
182
|
+
|
|
183
|
+
以下示例仅用 SDK 完成认证,其余所有操作(连接、握手、RPC 调用、事件接收)全部通过裸 WebSocket 实现:
|
|
184
|
+
|
|
185
|
+
```python
|
|
186
|
+
import asyncio, json, random, secrets
|
|
187
|
+
from datetime import datetime
|
|
188
|
+
from aun_core import AUNClient
|
|
189
|
+
from aun_core.errors import AUNError, ConnectionError, AuthError
|
|
190
|
+
import websockets
|
|
191
|
+
|
|
192
|
+
DOMAIN = "agentid.pub"
|
|
193
|
+
ALICE = f"alice{random.randint(1000,9999)}.{DOMAIN}"
|
|
194
|
+
BOB = f"bob{random.randint(1000,9999)}.{DOMAIN}"
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def ts():
|
|
198
|
+
return datetime.now().strftime("%H:%M:%S.%f")[:-3]
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def make_rpc(method: str, params: dict) -> tuple[str, str]:
|
|
202
|
+
"""构造 JSON-RPC 2.0 请求,返回 (rpc_id, json_str)"""
|
|
203
|
+
rpc_id = f"rpc-{secrets.token_hex(4)}"
|
|
204
|
+
msg = {"jsonrpc": "2.0", "id": rpc_id, "method": method, "params": params}
|
|
205
|
+
return rpc_id, json.dumps(msg)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
async def authenticate(aid: str) -> dict:
|
|
209
|
+
"""用 SDK 完成 AID 创建和认证,返回 auth 结果(含 access_token + gateway)"""
|
|
210
|
+
try:
|
|
211
|
+
client = AUNClient({"aun_path": f"~/.aun/{aid}"})
|
|
212
|
+
if not client._auth.load_identity_or_none(aid):
|
|
213
|
+
await client.auth.create_aid({"aid": aid})
|
|
214
|
+
auth = await client.auth.authenticate({"aid": aid})
|
|
215
|
+
return auth
|
|
216
|
+
except AuthError as e:
|
|
217
|
+
print(f"[错误] 认证失败 ({aid}): {e}")
|
|
218
|
+
raise
|
|
219
|
+
except ConnectionError as e:
|
|
220
|
+
print(f"[错误] 网络连接失败: {e}")
|
|
221
|
+
raise
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
async def ws_connect(gateway_url: str, token: str) -> websockets.ClientConnection:
|
|
225
|
+
"""建立 WebSocket 连接并完成握手(challenge → auth.connect → hello-ok)"""
|
|
226
|
+
ws = await websockets.connect(gateway_url, ping_interval=None)
|
|
227
|
+
|
|
228
|
+
# 1. 接收 challenge
|
|
229
|
+
raw = await ws.recv()
|
|
230
|
+
challenge = json.loads(raw)
|
|
231
|
+
nonce = challenge["params"]["nonce"]
|
|
232
|
+
|
|
233
|
+
# 2. 发送 auth.connect
|
|
234
|
+
rpc_id, req = make_rpc("auth.connect", {
|
|
235
|
+
"nonce": nonce,
|
|
236
|
+
"auth": {"method": "kite_token", "token": token},
|
|
237
|
+
"protocol": {"min": "1.0", "max": "1.0"},
|
|
238
|
+
})
|
|
239
|
+
await ws.send(req)
|
|
240
|
+
|
|
241
|
+
# 3. 接收 hello-ok
|
|
242
|
+
raw = await ws.recv()
|
|
243
|
+
hello = json.loads(raw)
|
|
244
|
+
if "error" in hello:
|
|
245
|
+
raise Exception(f"握手失败: {hello['error']}")
|
|
246
|
+
identity = hello["result"]["identity"]
|
|
247
|
+
print(f"[{ts()}] 已连接: {identity.get('aid')} (module={identity.get('module_id')})")
|
|
248
|
+
return ws
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
async def ws_call(ws, method: str, params: dict, timeout: float = 10.0) -> dict:
|
|
252
|
+
"""发送 RPC 调用并等待响应(从消息流中匹配 id)"""
|
|
253
|
+
rpc_id, req = make_rpc(method, params)
|
|
254
|
+
await ws.send(req)
|
|
255
|
+
deadline = asyncio.get_event_loop().time() + timeout
|
|
256
|
+
while True:
|
|
257
|
+
remaining = deadline - asyncio.get_event_loop().time()
|
|
258
|
+
if remaining <= 0:
|
|
259
|
+
raise TimeoutError(f"RPC 超时: {method}")
|
|
260
|
+
raw = await asyncio.wait_for(ws.recv(), timeout=remaining)
|
|
261
|
+
msg = json.loads(raw)
|
|
262
|
+
if msg.get("id") == rpc_id:
|
|
263
|
+
if "error" in msg:
|
|
264
|
+
raise Exception(f"RPC 错误: {msg['error']}")
|
|
265
|
+
return msg.get("result", {})
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
async def ws_wait_event(ws, event_name: str, timeout: float = 5.0) -> dict | None:
|
|
269
|
+
"""等待指定事件(method 字段以 event/ 为前缀)"""
|
|
270
|
+
target = f"event/{event_name}"
|
|
271
|
+
deadline = asyncio.get_event_loop().time() + timeout
|
|
272
|
+
while True:
|
|
273
|
+
remaining = deadline - asyncio.get_event_loop().time()
|
|
274
|
+
if remaining <= 0:
|
|
275
|
+
return None
|
|
276
|
+
try:
|
|
277
|
+
raw = await asyncio.wait_for(ws.recv(), timeout=remaining)
|
|
278
|
+
except asyncio.TimeoutError:
|
|
279
|
+
return None
|
|
280
|
+
msg = json.loads(raw)
|
|
281
|
+
if msg.get("method") == target:
|
|
282
|
+
return msg.get("params", {})
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
async def main():
|
|
286
|
+
alice_ws = None
|
|
287
|
+
bob_ws = None
|
|
288
|
+
|
|
289
|
+
try:
|
|
290
|
+
# ── 第一阶段:用 SDK 认证,拿到 token + gateway ──
|
|
291
|
+
alice_auth = await authenticate(ALICE)
|
|
292
|
+
bob_auth = await authenticate(BOB)
|
|
293
|
+
|
|
294
|
+
# ── 第二阶段:裸 WebSocket 连接(使用 auth 返回的 gateway)──
|
|
295
|
+
try:
|
|
296
|
+
alice_ws = await ws_connect(alice_auth["gateway"], alice_auth["access_token"])
|
|
297
|
+
bob_ws = await ws_connect(bob_auth["gateway"], bob_auth["access_token"])
|
|
298
|
+
except Exception as e:
|
|
299
|
+
print(f"[错误] WebSocket 连接失败: {e}")
|
|
300
|
+
raise
|
|
301
|
+
|
|
302
|
+
# ── 第三阶段:Alice 发消息 ──
|
|
303
|
+
try:
|
|
304
|
+
result = await ws_call(alice_ws, "message.send", {
|
|
305
|
+
"to": BOB,
|
|
306
|
+
"payload": {"type": "text", "text": "Hello from Alice (raw WebSocket)!"},
|
|
307
|
+
})
|
|
308
|
+
print(f"[{ts()}] [Alice 发送] status={result.get('status')}, seq={result.get('seq')}")
|
|
309
|
+
except TimeoutError as e:
|
|
310
|
+
print(f"[错误] RPC 调用超时: {e}")
|
|
311
|
+
except Exception as e:
|
|
312
|
+
print(f"[错误] 发送消息失败: {e}")
|
|
313
|
+
|
|
314
|
+
# ── 第四阶段:Bob 等待事件推送 ──
|
|
315
|
+
event = await ws_wait_event(bob_ws, "message.received", timeout=5.0)
|
|
316
|
+
if event:
|
|
317
|
+
print(f"[{ts()}] [Bob 收到事件] {event.get('payload')}")
|
|
318
|
+
else:
|
|
319
|
+
# 事件未推送,主动拉取
|
|
320
|
+
try:
|
|
321
|
+
pull = await ws_call(bob_ws, "message.pull", {"after_seq": 0, "limit": 10})
|
|
322
|
+
msgs = pull.get("messages", [])
|
|
323
|
+
if msgs:
|
|
324
|
+
print(f"[{ts()}] [Bob 拉取] 收到 {len(msgs)} 条消息:")
|
|
325
|
+
for m in msgs:
|
|
326
|
+
print(f" {m.get('payload')}")
|
|
327
|
+
else:
|
|
328
|
+
print(f"[{ts()}] [Bob] 未收到消息")
|
|
329
|
+
except Exception as e:
|
|
330
|
+
print(f"[错误] 拉取消息失败: {e}")
|
|
331
|
+
|
|
332
|
+
print(f"[{ts()}] 完成")
|
|
333
|
+
|
|
334
|
+
except KeyboardInterrupt:
|
|
335
|
+
print(f"\n[{ts()}] 用户中断")
|
|
336
|
+
except Exception as e:
|
|
337
|
+
print(f"[{ts()}] 程序异常: {e}")
|
|
338
|
+
raise
|
|
339
|
+
finally:
|
|
340
|
+
# ── 关闭 WebSocket ──
|
|
341
|
+
if alice_ws:
|
|
342
|
+
try:
|
|
343
|
+
await alice_ws.close()
|
|
344
|
+
except Exception:
|
|
345
|
+
pass
|
|
346
|
+
if bob_ws:
|
|
347
|
+
try:
|
|
348
|
+
await bob_ws.close()
|
|
349
|
+
except Exception:
|
|
350
|
+
pass
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
asyncio.run(main())
|
|
354
|
+
```
|