@my-life-buddies/buddy-runtime 0.15.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +205 -183
- package/dist/conversation/agent.d.ts.map +1 -1
- package/dist/conversation/agent.js +22 -7
- package/dist/conversation/agent.js.map +1 -1
- package/dist/conversation/index.d.ts +3 -1
- package/dist/conversation/index.d.ts.map +1 -1
- package/dist/conversation/index.js +13 -5
- package/dist/conversation/index.js.map +1 -1
- package/dist/conversation/mlbMessageAgentMessageConverter.d.ts +1 -0
- package/dist/conversation/mlbMessageAgentMessageConverter.d.ts.map +1 -1
- package/dist/conversation/mlbMessageAgentMessageConverter.js +5 -5
- package/dist/conversation/mlbMessageAgentMessageConverter.js.map +1 -1
- package/dist/conversation/writeMLBMessage.d.ts +1 -0
- package/dist/conversation/writeMLBMessage.d.ts.map +1 -1
- package/dist/conversation/writeMLBMessage.js.map +1 -1
- package/dist/dataAccessRequirements.d.ts +5 -0
- package/dist/dataAccessRequirements.d.ts.map +1 -0
- package/dist/dataAccessRequirements.js +53 -0
- package/dist/dataAccessRequirements.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/mlbClient.d.ts +10 -8
- package/dist/mlbClient.d.ts.map +1 -1
- package/dist/mlbClient.js +34 -6
- package/dist/mlbClient.js.map +1 -1
- package/dist/server.d.ts +2 -2
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +22 -32
- package/dist/server.js.map +1 -1
- package/dist/tools/askQuestion.d.ts +2 -1
- package/dist/tools/askQuestion.d.ts.map +1 -1
- package/dist/tools/askQuestion.js +4 -1
- package/dist/tools/askQuestion.js.map +1 -1
- package/dist/tools/dataAccess.d.ts +14 -4
- package/dist/tools/dataAccess.d.ts.map +1 -1
- package/dist/tools/dataAccess.js +60 -10
- package/dist/tools/dataAccess.js.map +1 -1
- package/dist/tools/dataAccessRequest.js +1 -1
- package/dist/tools/dataAccessRequest.js.map +1 -1
- package/dist/tools/proactive.d.ts +5 -26
- package/dist/tools/proactive.d.ts.map +1 -1
- package/dist/tools/proactive.js +82 -65
- package/dist/tools/proactive.js.map +1 -1
- package/dist/types.d.ts +35 -25
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ npm install @my-life-buddies/buddy-runtime typebox
|
|
|
20
20
|
|
|
21
21
|
### 1.2 创建搭子
|
|
22
22
|
|
|
23
|
-
通过 `@my-life-buddies/cli` 完成搭搭账号注册,并创建一个新的搭子,获取 buddyId
|
|
23
|
+
通过 `@my-life-buddies/cli` 完成搭搭账号注册,并创建一个新的搭子,获取 buddyId 与开发令牌(平台另有一把正式令牌,只在托管运行时注入,不经 CLI 下发)
|
|
24
24
|
|
|
25
25
|
### 1.3 搭子工程框架
|
|
26
26
|
|
|
@@ -49,48 +49,27 @@ const planMenu: AgentTool<typeof PLAN_MENU_PARAMS, undefined> = {
|
|
|
49
49
|
}),
|
|
50
50
|
};
|
|
51
51
|
|
|
52
|
-
await BuddyServer.start({
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
systemPrompt: "你是「小涂阿姨」——一位管做饭的阿姨……",
|
|
60
|
-
tools: [planMenu],
|
|
61
|
-
},
|
|
62
|
-
}),
|
|
63
|
-
});
|
|
52
|
+
await BuddyServer.start(() => ({
|
|
53
|
+
initialState: {
|
|
54
|
+
model: "kimi-k3",
|
|
55
|
+
systemPrompt: "你是「小涂阿姨」——一位管做饭的阿姨……",
|
|
56
|
+
tools: [planMenu],
|
|
57
|
+
},
|
|
58
|
+
}));
|
|
64
59
|
```
|
|
65
60
|
|
|
66
|
-
完整示例:[examples/aunt-tu/](examples/aunt-tu/)(提问、记忆、小挂件)、[examples/cardio/](examples/cardio/)(授权数据、主动服务、提问、记忆、小挂件)。
|
|
67
|
-
|
|
68
61
|
## 2. 配置
|
|
69
62
|
|
|
70
|
-
### 2.1 `BuddyServer.start(
|
|
63
|
+
### 2.1 `BuddyServer.start(buddy)`
|
|
71
64
|
|
|
72
65
|
连上平台开始处理消息,返回 `{ stop(): Promise<void> }`。
|
|
73
66
|
|
|
74
|
-
|
|
75
|
-
interface StartOptions {
|
|
76
|
-
// 必填。搭子 id 与令牌,注册时平台分配,见 1.2。平台托管时以环境变量 BUDDY_ID、BUDDY_TOKEN 注入
|
|
77
|
-
buddyId: string;
|
|
78
|
-
token: string;
|
|
79
|
-
|
|
80
|
-
// 必填。这只搭子需要的用户数据及用途,见 3.4;不需要时传 []
|
|
81
|
-
dataRequirements: { id: DataAccessDatasetId; purpose: string }[];
|
|
82
|
-
|
|
83
|
-
// 必填。每场会话建起来时调一次,返回这场会话的配置,见 2.2、2.3
|
|
84
|
-
buddy: (conversation: Conversation) => BuddyOptions | Promise<BuddyOptions>;
|
|
85
|
-
}
|
|
86
|
-
```
|
|
67
|
+
`buddy` 是每场会话建起来时调用一次的工厂函数,返回这场会话的配置,见 2.2、2.3。DataAccess 声明不属于会话工厂,单独写在工程根目录的 `data-access.json`,见 3.4。
|
|
87
68
|
|
|
88
|
-
Runtime
|
|
69
|
+
**buddyId 与 token**:Runtime 会从环境变量 `BUDDY_ID`、`BUDDY_TOKEN` 读。创建搭子时会下发 buddyId 与开发 token,本地开发时,启动命令携带环境变量,正式上线时平台托管会注入同名变量,token 会切换成正式 token,对开发者无感。
|
|
89
70
|
|
|
90
71
|
**buddy 的生命周期**:`buddy` 在每场会话收到第一条消息时调用一次。会话空闲 30 分钟被回收、或进程重启后,下一条消息会重新调用,所以闭包里的变量不能当长期存储,需要存储会话状态可以根据场景使用 3.3 记忆、3.5 小挂件实现。
|
|
91
72
|
|
|
92
|
-
**同一时间只有一个进程**:同一个搭子身份同一时间只有一个进程连着平台,本地调试用的 `<buddyId>-dev` 也一样。新进程连上平台后,原来那个进程会打一条 ERROR 日志说明被顶掉,然后退出、不再重连。本地不小心起了多个进程时,留下的是最后连上平台的那个。
|
|
93
|
-
|
|
94
73
|
**buddy 的会话压缩**:会话历史长到一定程度时,运行时在会话被回收后把早先的对话压成一份摘要写回平台,下次重建时模型看到的是「摘要 + 最近的原文」。开发者不用做任何事,也没有开关。
|
|
95
74
|
|
|
96
75
|
### 2.2 `BuddyOptions`
|
|
@@ -209,16 +188,17 @@ interface Conversation {
|
|
|
209
188
|
send(id: string, options: { idempotencyKey: string }): void;
|
|
210
189
|
};
|
|
211
190
|
|
|
212
|
-
// 主动服务,见 3.6
|
|
191
|
+
// 主动服务,见 3.6:按稳定 key 订阅、查询和关闭;isRunning 判断现在是不是主动服务的回合
|
|
213
192
|
proactive: {
|
|
214
|
-
|
|
193
|
+
subscribe(key: string, request: ProactiveSubscriptionRequest): Promise<ProactiveSubscription>;
|
|
194
|
+
list(): Promise<ProactiveSubscription[]>;
|
|
195
|
+
unsubscribe(key: string): Promise<boolean>;
|
|
196
|
+
unsubscribeAll(): Promise<number>;
|
|
215
197
|
isRunning(): boolean;
|
|
216
198
|
};
|
|
217
199
|
}
|
|
218
200
|
```
|
|
219
201
|
|
|
220
|
-
`tools`、`memory`、`dataAccess`、`widget`、`proactive` 已经绑好这场会话,直接用,不用传令牌或拼接口路径。
|
|
221
|
-
|
|
222
202
|
## 3. 平台能力
|
|
223
203
|
|
|
224
204
|
| 能力 | `conversation` 对象方法 | 模型工具(`conversation.tools` 上) |
|
|
@@ -228,7 +208,7 @@ interface Conversation {
|
|
|
228
208
|
| 记忆 | `conversation.memory` | 无,开发者可以根据具体场景,自己组装成适合的模型工具 |
|
|
229
209
|
| 用户授权数据 | `conversation.dataAccess.query` / `conversation.dataAccess.request` / `conversation.dataAccess.readResult` | `data_access_query` / `data_access_request` / `data_access_read_result` |
|
|
230
210
|
| 小挂件 | `conversation.widget.list` / `conversation.widget.create` / `conversation.widget.read` / `conversation.widget.readModule` / `conversation.widget.rename` / `conversation.widget.writeRecord` / `conversation.widget.deleteRecord` / `conversation.widget.delete` / `conversation.widget.setPresentation` / `conversation.widget.send` | `widget_create` / `widget_read` / `widget_read_module` / `widget_rename` / `widget_write_record` / `widget_delete_record` / `widget_delete` / `widget_send` |
|
|
231
|
-
| 主动服务 | `conversation.proactive.
|
|
211
|
+
| 主动服务 | `conversation.proactive.subscribe` / `list` / `unsubscribe` / `unsubscribeAll` / `isRunning` | `proactive_subscribe` / `proactive_list` / `proactive_unsubscribe` / `proactive_unsubscribe_all` |
|
|
232
212
|
|
|
233
213
|
```ts
|
|
234
214
|
buddy: (conversation) => ({
|
|
@@ -242,11 +222,11 @@ buddy: (conversation) => ({
|
|
|
242
222
|
|
|
243
223
|
### 3.1 资料
|
|
244
224
|
|
|
245
|
-
|
|
225
|
+
搭子随身带的只读资料:把 `.md` 放进工程目录下的 `resources/`,模型先列出有哪些资料,再按需读一篇。资料在进程启动时一次读好,整场会话、每个回合读到的都是同一份。
|
|
226
|
+
|
|
227
|
+
**准备工作**
|
|
246
228
|
|
|
247
|
-
|
|
248
|
-
- 启动时递归读取 `.md`,跳过 `node_modules`、`venv`、`.venv`、`__pycache__` 与点开头的文件和目录;资料改了要重启进程。
|
|
249
|
-
- `resource_list` 列出全部资料的相对路径,`resource_read` 按路径读一篇。没有资料时 `resource_list` 回「没有资料」。
|
|
229
|
+
资料放在工程目录(`package.json` 所在目录)下的 `resources/`:
|
|
250
230
|
|
|
251
231
|
```
|
|
252
232
|
my-buddy/ 工程目录
|
|
@@ -257,41 +237,63 @@ my-buddy/ 工程目录
|
|
|
257
237
|
└─ src/index.ts 入口脚本
|
|
258
238
|
```
|
|
259
239
|
|
|
260
|
-
|
|
240
|
+
- 资料更改要重启进程,建议 watch `resources/` 实时重启。
|
|
261
241
|
|
|
262
|
-
|
|
242
|
+
**`conversation` 对象方法**
|
|
263
243
|
|
|
264
244
|
| 方法 | 做什么 |
|
|
265
245
|
|---|---|
|
|
266
246
|
| `list()` | 全部资料的路径,相对 `resources/`,按路径排序 |
|
|
267
|
-
| `read(file)` | 读一篇的正文,`file` 照 `list()`
|
|
247
|
+
| `read(file)` | 读一篇的正文,`file` 照 `list()` 给的路径原样填 |
|
|
268
248
|
|
|
269
249
|
```ts
|
|
270
250
|
const notes = conversation.resource.read("research/01-writings.md");
|
|
271
251
|
```
|
|
272
252
|
|
|
253
|
+
**模型工具**
|
|
254
|
+
|
|
255
|
+
把 `conversation.tools.resource_list` 和 `conversation.tools.resource_read` 放进 `initialState.tools`,模型就能先列出有哪些资料,再按路径读一篇。`systemPrompt` 里交代清楚这只搭子带了哪类资料、什么时候该去翻。
|
|
256
|
+
|
|
257
|
+
**限制与异常**
|
|
258
|
+
|
|
273
259
|
- 两个方法都是同步的,读的是进程启动时读好的那份。
|
|
260
|
+
- 没有资料时 `resource_list` 回「没有资料」。
|
|
261
|
+
- `read(file)` 读不存在的那篇时抛异常。
|
|
274
262
|
- 主动服务的回合内也能调。
|
|
275
263
|
|
|
276
264
|
### 3.2 提问
|
|
277
265
|
|
|
278
|
-
让模型发一张题卡请用户选:一个问题加 2 到 4
|
|
266
|
+
让模型发一张题卡请用户选:一个问题加 2 到 4 个可以直接点的选项。私聊、群聊都能用。题卡发出,这一轮就到此结束,等用户操作。
|
|
267
|
+
|
|
268
|
+
**`conversation` 对象方法**
|
|
269
|
+
|
|
270
|
+
没有。`conversation` 不提供发题卡的方法,题卡只由模型发。
|
|
271
|
+
|
|
272
|
+
**模型工具**
|
|
279
273
|
|
|
280
274
|
把 `conversation.tools.ask_question` 放进 `initialState.tools`,模型就能发题卡。
|
|
281
275
|
|
|
282
|
-
- 参数是 `question`(题目,不超过 500 字)和 `options`(2 到 4 个,每个不超过 30
|
|
283
|
-
-
|
|
284
|
-
-
|
|
276
|
+
- 参数是 `question`(题目,不超过 500 字)和 `options`(2 到 4 个,每个不超过 30 字,不能重复)。
|
|
277
|
+
- 题目和选项只写在参数里,回复正文里不再说一遍。
|
|
278
|
+
- 用户点选项,就是用选项原文发一条消息,跟自己打字一样;也可以不点,直接打字回答,或者说别的。
|
|
279
|
+
|
|
280
|
+
**限制与异常**
|
|
281
|
+
|
|
282
|
+
- 参数不合规时模型会看到错误,改了再调。
|
|
283
|
+
- 这一轮读过用户授权数据之后不能再发题卡,调用抛异常,异常信息模型能看到;原因见 3.4 的「数据只在当轮可见」。
|
|
284
|
+
- 主动服务的回合里照常能发,运行时不拦。
|
|
285
285
|
|
|
286
286
|
### 3.3 记忆
|
|
287
287
|
|
|
288
|
-
|
|
288
|
+
这场会话的长期记忆,搭子写给自己的印象,存在平台上,平台只存不读。只在本会话内可见,会话删除时一并清除。
|
|
289
289
|
|
|
290
|
-
|
|
291
|
-
- `write(key, text)`:覆盖写一条。
|
|
292
|
-
- `delete(key)`:删一条,不存在也算成功。
|
|
290
|
+
**`conversation` 对象方法**
|
|
293
291
|
|
|
294
|
-
|
|
292
|
+
| 方法 | 做什么 |
|
|
293
|
+
|---|---|
|
|
294
|
+
| `list()` | 列出全部,按 `key` 字典序 |
|
|
295
|
+
| `write(key, text)` | 覆盖写一条 |
|
|
296
|
+
| `delete(key)` | 删一条,不存在也算成功 |
|
|
295
297
|
|
|
296
298
|
```ts
|
|
297
299
|
interface MemoryEntry {
|
|
@@ -301,46 +303,50 @@ interface MemoryEntry {
|
|
|
301
303
|
}
|
|
302
304
|
```
|
|
303
305
|
|
|
304
|
-
|
|
306
|
+
**模型工具**
|
|
307
|
+
|
|
308
|
+
运行时不提供记忆工具。要让模型自己记东西,按这只搭子要记什么,自己包一个:
|
|
305
309
|
|
|
306
310
|
```ts
|
|
307
311
|
const REMEMBER_PARAMS = Type.Object({ topic: Type.String(), text: Type.String() });
|
|
308
312
|
|
|
309
|
-
await BuddyServer.start({
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
};
|
|
322
|
-
return { initialState: { model: "kimi-k3", systemPrompt: PERSONA, tools: [remember] } };
|
|
323
|
-
},
|
|
313
|
+
await BuddyServer.start((conversation) => {
|
|
314
|
+
const remember: AgentTool<typeof REMEMBER_PARAMS, undefined> = {
|
|
315
|
+
name: "remember",
|
|
316
|
+
label: "正在记下",
|
|
317
|
+
description: "把一条关于对方的印象记下来,同一主题覆盖旧的。",
|
|
318
|
+
parameters: REMEMBER_PARAMS,
|
|
319
|
+
execute: async (_id, { topic, text }) => {
|
|
320
|
+
await conversation.memory.write(topic, text);
|
|
321
|
+
return { content: [{ type: "text", text: "记下了。" }], details: undefined };
|
|
322
|
+
},
|
|
323
|
+
};
|
|
324
|
+
return { initialState: { model: "kimi-k3", systemPrompt: PERSONA, tools: [remember] } };
|
|
324
325
|
});
|
|
325
326
|
```
|
|
326
327
|
|
|
328
|
+
**限制与异常**
|
|
329
|
+
|
|
330
|
+
- `key` 非空、不含空白与 `/`、最长 64 字;`text` 单条不超过 16 KB;每场会话最多 64 条、合计不超过 256 KB。
|
|
331
|
+
- 超限、`key` 不合法或网络出错时抛异常,异常信息是平台给出的原因,在工具里调用时模型能看到。
|
|
332
|
+
- 这一轮读过用户授权数据之后,运行时拒绝本回合的记忆写入,见 3.4 的「数据只在当轮可见」。
|
|
333
|
+
- 主动服务的回合里跟普通回合一样能读能写,运行时不拦。
|
|
334
|
+
|
|
327
335
|
### 3.4 用户授权数据
|
|
328
336
|
|
|
329
|
-
用户同意后,搭子可以读用户的睡眠、运动等数据。数据按 Dataset 分项,用户逐项同意。搭子拿不到用户的真实 id
|
|
337
|
+
用户同意后,搭子可以读用户的睡眠、运动等数据。数据按 Dataset 分项,用户逐项同意。搭子拿不到用户的真实 id。查到的数据只在当轮可见,见本节末尾。
|
|
330
338
|
|
|
331
339
|
这里有三层不同概念:
|
|
332
340
|
|
|
333
341
|
1. **Runtime 能力目录**:当前 SDK 支持哪些 Dataset,由包导出的 `DATA_ACCESS_CAPABILITIES` 提供。
|
|
334
|
-
2. **搭子数据声明**:这只搭子实际需要其中哪些 Dataset
|
|
342
|
+
2. **搭子数据声明**:这只搭子实际需要其中哪些 Dataset,以及各自用途,写在工程根目录的 `data-access.json`。
|
|
335
343
|
3. **用户 Grant**:用户是否同意这只搭子读取某个已声明 Dataset,由客户端授权流程管理,搭子不能代替用户授予。
|
|
336
344
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
位置和日历是当前会话的一次性授权结果,通过 `data_access_request` 请求,不属于 Dataset,也不写进 `dataRequirements`。HealthKit 数据会形成可重复查询的 Dataset,因此必须先在 `dataRequirements` 中声明。
|
|
345
|
+
位置和日历是当前会话的一次性授权结果,通过 `data_access_request` 请求,不属于 Dataset,也不写进 `data-access.json`。HealthKit 数据会形成可重复查询的 Dataset,因此必须先在配置中声明。
|
|
340
346
|
|
|
341
|
-
|
|
347
|
+
**准备工作**
|
|
342
348
|
|
|
343
|
-
|
|
349
|
+
Runtime 能力目录直接从 Runtime 包导入,开发者工具和项目代码都不需要网络请求:
|
|
344
350
|
|
|
345
351
|
```ts
|
|
346
352
|
import {
|
|
@@ -358,24 +364,25 @@ const sleep = dataAccessCapability("health.sleep");
|
|
|
358
364
|
|
|
359
365
|
这份导出是开发者可选 `id` 的权威来源。新增 Dataset 随 Runtime 版本发布;developer-platform、脚手架和预检都应读取该导出,不再维护一份手写枚举。
|
|
360
366
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
声明写在 `BuddyServer.start` 的初始化参数里,Runtime 在本地展开成带标题、来源和 Schema 版本的完整声明:
|
|
367
|
+
声明写在工程根目录(与 `package.json` 同级)的 `data-access.json`:
|
|
364
368
|
|
|
365
|
-
```
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
{ id: "health.sleep", purpose: "根据你最近的睡眠调整作息建议" }
|
|
369
|
-
]
|
|
370
|
-
|
|
371
|
-
});
|
|
369
|
+
```json
|
|
370
|
+
{
|
|
371
|
+
"requirements": [
|
|
372
|
+
{ "id": "health.sleep", "purpose": "根据你最近的睡眠调整作息建议" }
|
|
373
|
+
]
|
|
374
|
+
}
|
|
372
375
|
```
|
|
373
376
|
|
|
374
|
-
- `id`:从 `DATA_ACCESS_CAPABILITIES`
|
|
377
|
+
- `id`:从 `DATA_ACCESS_CAPABILITIES` 中选。
|
|
375
378
|
- `purpose`:用途,1~200 字。用户看到这句话再决定是否同意。
|
|
376
|
-
-
|
|
377
|
-
-
|
|
378
|
-
-
|
|
379
|
+
- `requirements` 最多 32 项,不允许重复;配置对象和每一项都不接受额外字段。
|
|
380
|
+
- 文件缺失等价于 `{ "requirements": [] }`。Runtime 启动时先严格校验,再把整份声明上传平台;即使为空也会上传,以清除旧声明。上传失败则启动失败。
|
|
381
|
+
- Server 在注册时按声明用途和 Schema 计算版本;`id`、`purpose` 与 Schema 都没变化时保留已有 Grant,变化后要求重新授权。
|
|
382
|
+
- 后续查询、HealthKit 权限卡和主动服务请求只发送 Dataset ID,不再重复携带用途声明。
|
|
383
|
+
- 本地 `.env` 里放的是 CLI 发的开发令牌,进程连上平台后身份是 `<buddyId>-dev`,实际使用形成的授权上下文与正式搭子隔离。
|
|
384
|
+
|
|
385
|
+
可以声明的 Dataset:
|
|
379
386
|
|
|
380
387
|
| Dataset | 内容 | `data.items` 每条的字段 |
|
|
381
388
|
|---|---|---|
|
|
@@ -390,34 +397,45 @@ await BuddyServer.start({
|
|
|
390
397
|
|
|
391
398
|
部分字段可能缺失,取值前先判断。
|
|
392
399
|
|
|
393
|
-
|
|
400
|
+
**`conversation` 对象方法**
|
|
394
401
|
|
|
395
|
-
|
|
396
|
-
- `data_access_request` / `conversation.dataAccess.request`:在私聊中发数据授权卡;发卡后当前回合结束,等待用户操作。
|
|
397
|
-
- `data_access_read_result` / `conversation.dataAccess.readResult`:按授权卡的 `requestId` 读取一次性位置或日历结果;HealthKit 不走这个接口。
|
|
402
|
+
开发者自己的工具直接调用 `conversation.dataAccess`,不用把 `data_access_*` 放进 `initialState.tools`:
|
|
398
403
|
|
|
399
|
-
|
|
404
|
+
| 方法 | 做什么 |
|
|
405
|
+
|---|---|
|
|
406
|
+
| `query({ datasets, memberId? })` | 查询已授权 Dataset,每个 Dataset 各自一个状态 |
|
|
407
|
+
| `request({ kind, reason, datasets?, calendarDays?, idempotencyKey })` | 在私聊里发数据授权卡,`kind` 取 `healthkit`、`location` 或 `calendar` |
|
|
408
|
+
| `readResult(requestId)` | 按授权卡的 `requestId` 读一次性的位置或日历结果;HealthKit 不走这个接口 |
|
|
400
409
|
|
|
401
|
-
|
|
410
|
+
```ts
|
|
411
|
+
const [sleep] = await conversation.dataAccess.query({ datasets: ["health.sleep"] });
|
|
412
|
+
if (sleep.status === "available") {
|
|
413
|
+
// sleep.data.items 是每天一条的睡眠记录
|
|
414
|
+
}
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
- `datasets`:一次最多 8 个。
|
|
418
|
+
- `memberId`:私聊不传;群聊必传,取群消息开头 `[名字 #m_…]` 里的 `m_…`,查这位成员的数据,传错时抛异常。
|
|
419
|
+
|
|
420
|
+
**模型工具**
|
|
421
|
+
|
|
422
|
+
把 `conversation.tools.data_access_query`、`data_access_request`、`data_access_read_result` 放进 `initialState.tools`,模型就能自己查数据、发授权卡。放进去只决定模型能不能调用它们,不等于声明 Dataset,也不等于获得用户授权;没声明 Dataset 时照常注册,查询的每一项都回 `notDeclared`。
|
|
423
|
+
|
|
424
|
+
HealthKit 先查询,缺授权再发卡:
|
|
402
425
|
|
|
403
426
|
```ts
|
|
404
|
-
await BuddyServer.start({
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
].join("\n"),
|
|
417
|
-
tools: [conversation.tools.data_access_query, conversation.tools.data_access_request],
|
|
418
|
-
},
|
|
419
|
-
}),
|
|
420
|
-
});
|
|
427
|
+
await BuddyServer.start((conversation) => ({
|
|
428
|
+
initialState: {
|
|
429
|
+
model: "kimi-k3",
|
|
430
|
+
systemPrompt: [
|
|
431
|
+
"聊到作息时,先用 data_access_query 查询 health.sleep。",
|
|
432
|
+
"返回 notGranted 时,说明当前问题为什么需要数据,再用 data_access_request 发 HealthKit 授权卡。",
|
|
433
|
+
"用户处理授权卡后会开启新回合;再次调用 data_access_query,不要调用 data_access_read_result。",
|
|
434
|
+
"用户拒绝后不要在同一任务里反复申请。",
|
|
435
|
+
].join("\n"),
|
|
436
|
+
tools: [conversation.tools.data_access_query, conversation.tools.data_access_request],
|
|
437
|
+
},
|
|
438
|
+
}));
|
|
421
439
|
```
|
|
422
440
|
|
|
423
441
|
完整链路:
|
|
@@ -432,11 +450,7 @@ data_access_query
|
|
|
432
450
|
→ available + data
|
|
433
451
|
```
|
|
434
452
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
**位置和日历:读取一次性结果**
|
|
438
|
-
|
|
439
|
-
位置和日历不属于 Dataset,不写进 `dataRequirements`。模型先用 `data_access_request` 发卡,用户处理后,隐藏结果会带回这张卡的 `requestId`;再用 `data_access_read_result` 读取。结果只在平台短期保存,读不到时返回 `unavailable`。
|
|
453
|
+
位置和日历不属于 Dataset,不写进 `data-access.json`。模型先用 `data_access_request` 发卡,用户处理后,隐藏结果会带回这张卡的 `requestId`;再用 `data_access_read_result` 读取。结果只在平台短期保存,读不到时返回 `unavailable`。
|
|
440
454
|
|
|
441
455
|
```text
|
|
442
456
|
data_access_request { kind: "location", reason: "查找你附近的地点" }
|
|
@@ -446,19 +460,9 @@ data_access_request { kind: "location", reason: "查找你附近的地点" }
|
|
|
446
460
|
→ available + data,或 unavailable
|
|
447
461
|
```
|
|
448
462
|
|
|
449
|
-
`calendar` 还可传 `calendarDays`,范围为 1~31,默认 7
|
|
463
|
+
`calendar` 还可传 `calendarDays`,范围为 1~31,默认 7。
|
|
450
464
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
```ts
|
|
454
|
-
const [sleep] = await conversation.dataAccess.query({ datasets: ["health.sleep"] });
|
|
455
|
-
if (sleep.status === "available") {
|
|
456
|
-
// sleep.data.items 是每天一条的睡眠记录
|
|
457
|
-
}
|
|
458
|
-
```
|
|
459
|
-
|
|
460
|
-
- `datasets`:一次最多 8 个;响应超过 512 KiB 时抛异常,减少 Dataset 再查。
|
|
461
|
-
- `memberId`:私聊不传;群聊必传,取群消息开头 `[名字 #m_…]` 里的 `m_…`,查这位成员的数据,传错时抛异常。
|
|
465
|
+
**限制与异常**
|
|
462
466
|
|
|
463
467
|
每个 Dataset 各自一个状态,只有 `available` 带 `data`:
|
|
464
468
|
|
|
@@ -470,22 +474,27 @@ if (sleep.status === "available") {
|
|
|
470
474
|
| `disabledInGroup` | 这位成员在这个群里关掉了这项数据 |
|
|
471
475
|
| `unavailable` | 平台上还没有这位用户的这项数据,或数据已过期 |
|
|
472
476
|
|
|
477
|
+
- 查询接口不会把「从未授权」和「用途变化后需重新确认」分成两种模型状态,都会返回 `notGranted`;重新确认原因由用户侧授权页面展示。
|
|
478
|
+
- 一次查询的响应超过 512 KiB 时抛异常,减少 Dataset 再查。
|
|
479
|
+
- 发出授权卡后当前回合结束,等待用户操作。
|
|
480
|
+
- 授权请求第一期只支持私聊;群聊需要引导目标成员去私聊操作。
|
|
481
|
+
|
|
473
482
|
**数据只在当轮可见**
|
|
474
483
|
|
|
475
|
-
|
|
484
|
+
查到的数据只用于这一轮回复:平台不存原文,搭子进程里的那份也在这一轮结束时被抹掉,下一轮读不到。Runtime 按步骤追踪数据流:读取数据之前已经生成的工具步骤正常保留;DataAccess 结果只保留 Dataset 状态;模型看到数据后生成的工具参数、工具结果、工具步骤正文和心声不写进平台,最终给用户看的普通回复只保留正文、不保留心声。
|
|
476
485
|
|
|
477
|
-
|
|
486
|
+
这么设计是为了**用户撤销授权时能真的收回**:确保模型不会记住授权的具体数据。
|
|
478
487
|
|
|
479
488
|
由此有两条实操结论:
|
|
480
489
|
|
|
481
490
|
- **想让某个结论以后还在场,就在回复里说出来**(用户也会看到)。模型对这些数据的记忆只能活在它说过的话里——下次它读到的是「查了 health.sleep,可用」,不是具体数字。
|
|
482
|
-
-
|
|
491
|
+
- **不要把查到的数据写进记忆、题卡、小挂件或别的工具参数**。Runtime 会直接拒绝当前回合的记忆写入和题卡发送,并脱敏后续工具调用;其他开发者自定义持久化能力仍需自行遵守这条边界。
|
|
483
492
|
|
|
484
493
|
### 3.5 小挂件
|
|
485
494
|
|
|
486
|
-
|
|
495
|
+
小挂件是会话里一块给用户看的结构化面板,比如一份菜单。建小挂件、读整个或一个模块、改标题、写记录、删记录、删整个小挂件,并把它发进聊天,模型和开发者代码都能做。
|
|
487
496
|
|
|
488
|
-
|
|
497
|
+
**准备工作**
|
|
489
498
|
|
|
490
499
|
一个小挂件是工程目录下 `widgets/` 里的一个子目录,目录名就是小挂件类型 id。
|
|
491
500
|
|
|
@@ -499,18 +508,22 @@ my-buddy/ 工程目录
|
|
|
499
508
|
└─ src/index.ts
|
|
500
509
|
```
|
|
501
510
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
把要用的 `conversation.tools.widget_*` 放进 `initialState.tools`。
|
|
505
|
-
|
|
506
|
-
- 工具说明里附上各类型的模块与字段。改了 `widgets/` 要重启进程。
|
|
507
|
-
- 这场会话里已有哪些小挂件,平台会在消息里告诉模型。
|
|
508
|
-
- 平台请求失败时,模型只看到「平台请求失败(HTTP 409),请查询最新状态后再操作」这类提示,不带具体原因。
|
|
509
|
-
- `widget_send` 把小挂件放进发送队列就返回,不等平台确认送达。
|
|
511
|
+
资料小挂件要重启进程,建议 watch `widgets/` 实时重启。
|
|
510
512
|
|
|
511
|
-
|
|
513
|
+
**`conversation` 对象方法**
|
|
512
514
|
|
|
513
|
-
|
|
515
|
+
| 方法 | 做什么 |
|
|
516
|
+
|---|---|
|
|
517
|
+
| `list()` | 列出这场会话里的小挂件,不带记录 |
|
|
518
|
+
| `create({ type, title, idempotencyKey? })` | 新建一个,回 `{ id }`;同一个 `idempotencyKey` 重复建回同一个 id |
|
|
519
|
+
| `read(id)` | 读整个小挂件,含各模块的记录 |
|
|
520
|
+
| `readModule(id, module)` | 读一个模块,回 `{ records }` |
|
|
521
|
+
| `rename(id, title)` | 改标题,id 不变,聊天里发过的卡片跟着显示新标题 |
|
|
522
|
+
| `writeRecord(id, { module, recordId, data, expectedRevision? })` | 写一条记录,已存在就整条覆盖,回 `{ revision }`;带 `expectedRevision` 时版本号对不上,平台回 409 |
|
|
523
|
+
| `deleteRecord(id, module, recordId)` | 删一条记录 |
|
|
524
|
+
| `delete(id)` | 删整个小挂件连记录;聊天里发过的卡片留着,点开显示已删除 |
|
|
525
|
+
| `setPresentation(id, { summary, imageMediaId })` | 设聊天里卡片的摘要(不超过 500 字)和封面图,`imageMediaId` 传空串去掉封面 |
|
|
526
|
+
| `send(id, { idempotencyKey })` | 发进聊天:放进发送队列就返回,不等平台确认送达;同一个 `idempotencyKey` 重复发只落一条 |
|
|
514
527
|
|
|
515
528
|
```ts
|
|
516
529
|
execute: async (toolCallId, params) => {
|
|
@@ -521,18 +534,16 @@ execute: async (toolCallId, params) => {
|
|
|
521
534
|
},
|
|
522
535
|
```
|
|
523
536
|
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
| `setPresentation(id, { summary, imageMediaId })` | 设聊天里卡片的摘要(不超过 500 字)和封面图,`imageMediaId` 传空串去掉封面 | `PUT /internal/v1/widgets/:widgetId/presentation` |
|
|
535
|
-
| `send(id, { idempotencyKey })` | 发进聊天:放进发送队列就返回,不等平台确认送达;同一个 `idempotencyKey` 重复发只落一条 | `POST /internal/v1/messages` |
|
|
537
|
+
**模型工具**
|
|
538
|
+
|
|
539
|
+
把要用的 `conversation.tools.widget_*` 放进 `initialState.tools`,模型就能自己建、读、改、发小挂件。
|
|
540
|
+
|
|
541
|
+
- 工具说明里附上各类型的模块与字段,模型不用另外交代形状。
|
|
542
|
+
- 这场会话里已有哪些小挂件,平台会在消息里告诉模型。
|
|
543
|
+
- 平台请求失败时,模型只看到「平台请求失败(HTTP 409),请查询最新状态后再操作」这类提示,不带具体原因。
|
|
544
|
+
- `widget_send` 把小挂件放进发送队列就返回,不等平台确认送达。
|
|
545
|
+
|
|
546
|
+
**限制与异常**
|
|
536
547
|
|
|
537
548
|
- `list` 和 `read` 回的 `canEdit` 为 `false` 的,是别的会话转发来的引用,只能读;改名、写记录、删除、设卡片时平台回 403。
|
|
538
549
|
- `data` 要符合 `schema.json` 里这个模块的 `record`,不符合时平台回 400。
|
|
@@ -543,53 +554,64 @@ execute: async (toolCallId, params) => {
|
|
|
543
554
|
|
|
544
555
|
让搭子在约定的时机主动找用户说话,比如每周一次复盘。目前主动服务仅支持私聊。
|
|
545
556
|
|
|
546
|
-
1.
|
|
557
|
+
1. 搭子按稳定 `key` 向平台创建或更新一条主动服务订阅,其中包含触发条件;相同 `key` 不会重复创建。
|
|
547
558
|
2. 触发条件满足,且没被免打扰时段、冷却时间、每日上限拦下时,平台向搭子发起一次主动服务的回合。
|
|
548
559
|
3. 模型判断要不要开口:要就直接写出发给用户的消息,平台在落库那一刻再过一遍闸门,过了才发给用户,拦下就不发;不要就输出跳过标记,这一轮到此结束,用户什么也看不到。
|
|
549
560
|
|
|
550
|
-
|
|
561
|
+
**`conversation` 对象方法**
|
|
551
562
|
|
|
552
|
-
|
|
563
|
+
| 方法 | 做什么 |
|
|
564
|
+
|---|---|
|
|
565
|
+
| `subscribe(key, request)` | 按稳定 `key` 创建或更新一条订阅,相同 `key` 更新原订阅并重新启用 |
|
|
566
|
+
| `list()` | 当前会话的全部订阅,包括已关闭项 |
|
|
567
|
+
| `unsubscribe(key)` | 关闭指定订阅,回是否找到 |
|
|
568
|
+
| `unsubscribeAll()` | 关闭全部 active 订阅,回关掉几条 |
|
|
569
|
+
| `isRunning()` | 现在是不是主动服务的回合 |
|
|
570
|
+
|
|
571
|
+
它挂在这场会话的 `conversation` 上,凡是拿得到 `conversation` 的地方都能调:自己工具的 `execute` 里、`beforeToolCall` / `afterToolCall` 里、配置工厂函数体里。
|
|
553
572
|
|
|
554
573
|
```ts
|
|
555
|
-
await conversation.proactive.
|
|
574
|
+
await conversation.proactive.subscribe("weekly-review", {
|
|
556
575
|
title: "每周复盘",
|
|
557
576
|
reason: "在约定时间结合最近聊天做一次简短复盘",
|
|
558
|
-
|
|
559
|
-
eventTypes: ["schedule.due"],
|
|
560
|
-
schedule: { at: firstReviewAt, intervalMinutes: 7 * 24 * 60 },
|
|
577
|
+
trigger: { type: "schedule", at: firstReviewAt, intervalMinutes: 7 * 24 * 60 },
|
|
561
578
|
});
|
|
562
579
|
```
|
|
563
580
|
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
```ts
|
|
567
|
-
await BuddyServer.start({
|
|
568
|
-
dataRequirements: [{ id: "health.workouts", purpose: "跑完后帮你复盘这次训练" }],
|
|
569
|
-
buddy: (conversation) => ({
|
|
570
|
-
initialState: {
|
|
571
|
-
model: "kimi-k3",
|
|
572
|
-
systemPrompt: "你是跑步搭子。用户表达想坚持训练时,可以用 proactive_propose 提议跑完步帮他复盘。",
|
|
573
|
-
tools: [conversation.tools.proactive_propose, conversation.tools.data_access_query],
|
|
574
|
-
},
|
|
575
|
-
}),
|
|
576
|
-
});
|
|
577
|
-
```
|
|
581
|
+
`subscribe` 的字段:
|
|
578
582
|
|
|
579
583
|
| 字段 | 说明 |
|
|
580
584
|
|---|---|
|
|
585
|
+
| `key` | 必填,订阅的稳定标识:1~64 位小写字母、数字、点、下划线或横线,首位为字母或数字;相同 `key` 更新原订阅并重新启用 |
|
|
581
586
|
| `title` | 必填,订阅名称,最长 100 字 |
|
|
582
587
|
| `reason` | 必填,为什么要主动找用户,最长 500 字 |
|
|
583
|
-
| `
|
|
584
|
-
| `eventTypes` | 必填,至少一个,触发事件类型 |
|
|
585
|
-
| `schedule` | 可选,定时触发:`at` 为首次时刻(UTC 毫秒),`intervalMinutes` 为重复间隔(60~525600),按固定间隔算,不随夏令时调整。用它时 `eventTypes` 要含 `schedule.due`,`sources` 为 `[]` 或含 `schedule` |
|
|
588
|
+
| `trigger` | 必填,什么时候触发:`workout.completed`、`sleep.ready`、`{ type: "data.updated", dataset? }`,或 `{ type: "schedule", at, intervalMinutes? }`。一条订阅只对应一种触发 |
|
|
586
589
|
| `quietHours` | 可选,免打扰时段,缺省 `{ start: "22:30", end: "08:00", timezone: "Asia/Shanghai" }` |
|
|
587
590
|
| `cooldownMinutes` | 可选,冷却时间,缺省 120,取 0~10080 |
|
|
588
591
|
| `dailyLimit` | 可选,每天最多几次,缺省 3,取 1~20 |
|
|
589
592
|
|
|
590
|
-
|
|
593
|
+
**模型工具**
|
|
594
|
+
|
|
595
|
+
让模型自己决定是否订阅或关闭时,把需要的 `conversation.tools.proactive_*` 放进 `initialState.tools`,字段与上面那张表一样。什么场景可以订阅、何时只能查询或关闭,要在 `systemPrompt` 里说清楚:
|
|
596
|
+
|
|
597
|
+
```ts
|
|
598
|
+
await BuddyServer.start((conversation) => ({
|
|
599
|
+
initialState: {
|
|
600
|
+
model: "kimi-k3",
|
|
601
|
+
systemPrompt: "你是跑步搭子。用户表达想坚持训练时,可以用 proactive_subscribe 订阅跑后复盘;用户要求关闭时先查询再关闭。",
|
|
602
|
+
tools: [conversation.tools.proactive_subscribe, conversation.tools.proactive_list, conversation.tools.proactive_unsubscribe, conversation.tools.data_access_query],
|
|
603
|
+
},
|
|
604
|
+
}));
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
代码订阅和模型订阅二选一或并用,产物都是一条订阅,只是谁决定何时订阅不同。
|
|
608
|
+
|
|
609
|
+
**限制与异常**
|
|
610
|
+
|
|
611
|
+
- 字段不合法、在群聊里调用,或同一用户对这只搭子已有 20 个 active 订阅时抛异常。
|
|
612
|
+
- 关闭订阅时,尚未完成的主动服务会同步取消。
|
|
591
613
|
|
|
592
|
-
|
|
614
|
+
**主动服务的回合,不允许使用部分模型工具**
|
|
593
615
|
|
|
594
616
|
主动服务的回合里工具清单一个字都不动,平台工具和你自己的工具都照常执行,运行时不按回合性质拦任何一个。有时我们想禁用部分模型工具,比如一些写操作,可以使用 `beforeToolCall` 的钩子函数,用 `conversation.proactive.isRunning()` 能判断当前是不是主动服务的回合:
|
|
595
617
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/conversation/agent.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,KAAK,EAAE,MAAM,+BAA+B,CAAC;AACtD,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AAExF,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAGxD,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAG7D,OAAO,EAAkB,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAEhF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAKrD,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAA8B,eAAe,EAAE,iBAAiB,EAAiB,MAAM,aAAa,CAAC;AAE5I,MAAM,WAAW,SAAS;IACzB,mCAAmC;IACnC,cAAc,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,IAAI,CAAC;IACrD,wCAAwC;IACxC,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,SAAS,CAAC;IAClB,uEAAuE;IACvE,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,QAAQ,CAAC;IACnB,iEAAiE;IACjE,KAAK,EAAE,MAAM,CAAC;IACd,+DAA+D;IAC/D,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,uDAAuD;IACvD,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,gBAAgB,EAAE,SAAS,eAAe,EAAE,CAAC;IAC7C,OAAO,EAAE,YAAY,CAAC;IACtB,GAAG,EAAE,WAAW,CAAC;IACjB,oCAAoC;IACpC,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;CACnD;AAED,wDAAwD;AACxD,MAAM,WAAW,YAAY;IAC5B,KAAK,EAAE,KAAK,CAAC;IACb,yDAAyD;IACzD,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IAClB,eAAe,EAAE,sBAAsB,CAAC;IACxC,iFAAiF;IACjF,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;CAC/B;AAED,kFAAkF;AAClF,wBAAsB,WAAW,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,YAAY,CAAC,
|
|
1
|
+
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/conversation/agent.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,KAAK,EAAE,MAAM,+BAA+B,CAAC;AACtD,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AAExF,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAGxD,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAG7D,OAAO,EAAkB,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAEhF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAKrD,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAA8B,eAAe,EAAE,iBAAiB,EAAiB,MAAM,aAAa,CAAC;AAE5I,MAAM,WAAW,SAAS;IACzB,mCAAmC;IACnC,cAAc,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,IAAI,CAAC;IACrD,wCAAwC;IACxC,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,SAAS,CAAC;IAClB,uEAAuE;IACvE,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,QAAQ,CAAC;IACnB,iEAAiE;IACjE,KAAK,EAAE,MAAM,CAAC;IACd,+DAA+D;IAC/D,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,uDAAuD;IACvD,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,gBAAgB,EAAE,SAAS,eAAe,EAAE,CAAC;IAC7C,OAAO,EAAE,YAAY,CAAC;IACtB,GAAG,EAAE,WAAW,CAAC;IACjB,oCAAoC;IACpC,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;CACnD;AAED,wDAAwD;AACxD,MAAM,WAAW,YAAY;IAC5B,KAAK,EAAE,KAAK,CAAC;IACb,yDAAyD;IACzD,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IAClB,eAAe,EAAE,sBAAsB,CAAC;IACxC,iFAAiF;IACjF,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;CAC/B;AAED,kFAAkF;AAClF,wBAAsB,WAAW,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,YAAY,CAAC,CAwMxE;AAED;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,iHAAuB,CAAC;AAEtD,iEAAiE;AACjE,wBAAgB,eAAe,CAAC,QAAQ,EAAE,YAAY,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,YAAY,EAAE,CAErF;AAED,iDAAiD;AACjD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAQxD"}
|