@my-life-buddies/buddy-runtime 0.9.0 → 0.10.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 +200 -102
- package/dist/conversation/agent.d.ts +10 -4
- package/dist/conversation/agent.d.ts.map +1 -1
- package/dist/conversation/agent.js +93 -68
- package/dist/conversation/agent.js.map +1 -1
- package/dist/conversation/compaction.d.ts +2 -2
- package/dist/conversation/compaction.d.ts.map +1 -1
- package/dist/conversation/compaction.js +1 -1
- package/dist/conversation/compaction.js.map +1 -1
- package/dist/conversation/index.d.ts +12 -10
- package/dist/conversation/index.d.ts.map +1 -1
- package/dist/conversation/index.js +20 -20
- package/dist/conversation/index.js.map +1 -1
- package/dist/dataAccessCatalog.d.ts +56 -0
- package/dist/dataAccessCatalog.d.ts.map +1 -0
- package/dist/dataAccessCatalog.js +18 -0
- package/dist/dataAccessCatalog.js.map +1 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/mlbClient.d.ts +5 -7
- package/dist/mlbClient.d.ts.map +1 -1
- package/dist/mlbClient.js +6 -18
- package/dist/mlbClient.js.map +1 -1
- package/dist/model.d.ts +8 -1
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +12 -6
- package/dist/model.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +18 -24
- package/dist/server.js.map +1 -1
- package/dist/tools/dataAccess.d.ts +1 -1
- package/dist/tools/dataAccess.d.ts.map +1 -1
- package/dist/tools/dataAccess.js +4 -4
- package/dist/tools/dataAccess.js.map +1 -1
- package/dist/tools/dataAccessRequest.d.ts +7 -0
- package/dist/tools/dataAccessRequest.d.ts.map +1 -0
- package/dist/tools/{permissions.js → dataAccessRequest.js} +7 -7
- package/dist/tools/dataAccessRequest.js.map +1 -0
- package/dist/tools/registry.d.ts +1 -4
- package/dist/tools/registry.d.ts.map +1 -1
- package/dist/tools/registry.js +1 -15
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/resource.d.ts +4 -3
- package/dist/tools/resource.d.ts.map +1 -1
- package/dist/tools/resource.js +18 -13
- package/dist/tools/resource.js.map +1 -1
- package/dist/tools/{widgets.d.ts → widget.d.ts} +6 -4
- package/dist/tools/widget.d.ts.map +1 -0
- package/dist/tools/{widgets.js → widget.js} +13 -9
- package/dist/tools/widget.js.map +1 -0
- package/dist/types.d.ts +52 -33
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +5 -3
- package/dist/types.js.map +1 -1
- package/package.json +2 -2
- package/dist/tools/permissions.d.ts +0 -7
- package/dist/tools/permissions.d.ts.map +0 -1
- package/dist/tools/permissions.js.map +0 -1
- package/dist/tools/widgets.d.ts.map +0 -1
- package/dist/tools/widgets.js.map +0 -1
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ npm install @my-life-buddies/buddy-runtime typebox
|
|
|
14
14
|
|
|
15
15
|
### 1.2 注册搭子
|
|
16
16
|
|
|
17
|
-
通过平台开发者接口 `POST /cli/buddies` 注册,提交名称、头像、简介,平台分配搭子 id 与令牌;之后改档案用 `PUT /cli/buddies/:id/meta
|
|
17
|
+
通过平台开发者接口 `POST /cli/buddies` 注册,提交名称、头像、简介,平台分配搭子 id 与令牌;之后改档案用 `PUT /cli/buddies/:id/meta`。用哪个模型在 `initialState.model` 里选,见 2.2。可声明的数据能力由 Runtime 导出的 `DATA_ACCESS_CAPABILITIES` 提供。
|
|
18
18
|
|
|
19
19
|
### 1.3 搭子工程框架
|
|
20
20
|
|
|
@@ -26,8 +26,8 @@ my-buddy/
|
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
```ts
|
|
29
|
-
import type { AgentTool } from "@earendil-works/pi-agent-core";
|
|
30
29
|
import { BuddyServer } from "@my-life-buddies/buddy-runtime";
|
|
30
|
+
import type { AgentTool } from "@my-life-buddies/buddy-runtime";
|
|
31
31
|
import { Type } from "typebox";
|
|
32
32
|
|
|
33
33
|
const PLAN_MENU_PARAMS = Type.Object({ date: Type.String() });
|
|
@@ -44,10 +44,10 @@ const planMenu: AgentTool<typeof PLAN_MENU_PARAMS, undefined> = {
|
|
|
44
44
|
};
|
|
45
45
|
|
|
46
46
|
await BuddyServer.start({
|
|
47
|
-
model: "kimi-k3",
|
|
48
47
|
dataRequirements: [],
|
|
49
48
|
buddy: () => ({
|
|
50
49
|
initialState: {
|
|
50
|
+
model: "kimi-k3",
|
|
51
51
|
systemPrompt: "你是「小涂阿姨」——一位管做饭的阿姨……",
|
|
52
52
|
tools: [planMenu],
|
|
53
53
|
},
|
|
@@ -83,14 +83,17 @@ await BuddyServer.start({
|
|
|
83
83
|
|
|
84
84
|
连上平台开始处理消息,返回 `{ stop(): Promise<void> }`。
|
|
85
85
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
| `buddy` | 必填,`(conversation: Conversation) => BuddyOptions \| Promise<BuddyOptions>`,见 2.2、2.3 |
|
|
91
|
-
| `errorReply` | 可选,模型出错或空回复时发的兜底回复 |
|
|
86
|
+
```ts
|
|
87
|
+
interface StartOptions {
|
|
88
|
+
// 必填。这只搭子需要的用户数据及用途,见 3.4;不需要时传 []
|
|
89
|
+
dataRequirements: { id: DataAccessDatasetId; purpose: string }[];
|
|
92
90
|
|
|
93
|
-
|
|
91
|
+
// 必填。每场会话建起来时调一次,返回这场会话的配置,见 2.2、2.3
|
|
92
|
+
buddy: (conversation: Conversation) => BuddyOptions | Promise<BuddyOptions>;
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Runtime 启动时只在本地校验 `dataRequirements`,不会额外调用接口注册声明。Runtime 根据自身导出的能力目录补齐标题、来源和 Schema 版本;实际调用 `data_access_query`、发送 HealthKit 权限卡或申请使用数据的主动服务时,会把完整声明随业务请求携带给平台。平台在该次请求中更新用途版本并校验 Grant。
|
|
94
97
|
|
|
95
98
|
用户新加搭子、新建群时,搭子会先开口打个招呼,跟平时回复一样调模型、逐字出现。开场白怎么说,写在 system prompt 里。
|
|
96
99
|
|
|
@@ -100,37 +103,52 @@ Runtime 启动时先校验 `dataRequirements`,再凭搭子令牌把整份声
|
|
|
100
103
|
|
|
101
104
|
### 2.2 `BuddyOptions`
|
|
102
105
|
|
|
103
|
-
`buddy`
|
|
106
|
+
`buddy` 的返回值。
|
|
104
107
|
|
|
105
108
|
```ts
|
|
106
|
-
type
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
109
|
+
import type {
|
|
110
|
+
AfterToolCallContext,
|
|
111
|
+
AfterToolCallResult,
|
|
112
|
+
AgentTool,
|
|
113
|
+
BeforeToolCallContext,
|
|
114
|
+
BeforeToolCallResult,
|
|
115
|
+
ModelId,
|
|
116
|
+
ShouldStopAfterTurnContext,
|
|
117
|
+
ThinkingLevel,
|
|
118
|
+
} from "@my-life-buddies/buddy-runtime";
|
|
119
|
+
|
|
120
|
+
interface BuddyOptions {
|
|
121
|
+
// 必填。这场会话的初始状态
|
|
122
|
+
initialState: {
|
|
123
|
+
// 必填。模型 id,ModelId 是 "kimi-k2.6" | "kimi-k3" | "deepseek-v4-flash" | "deepseek-v4-pro"。每场会话可以选不同的模型
|
|
124
|
+
model: ModelId;
|
|
125
|
+
|
|
126
|
+
// 必填。人设与做事规则。为保证模型缓存,只放不随回合变化的内容
|
|
127
|
+
systemPrompt: string;
|
|
128
|
+
|
|
129
|
+
// 可选。模型只看得到这里的工具;平台工具从 conversation.tools 里取来放进去,见第 3 节
|
|
130
|
+
tools?: AgentTool[];
|
|
131
|
+
|
|
132
|
+
// 可选,默认 "off"。ThinkingLevel 是 "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max"。
|
|
133
|
+
// 模型不支持的级别在请求时就近换成支持的一档,建会话时打一条 warn
|
|
134
|
+
thinkingLevel?: ThinkingLevel;
|
|
135
|
+
};
|
|
116
136
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
| `initialState.systemPrompt` | 必填,人设与做事规则,只放不随回合变化的内容。模型实际收到的系统提示见第 4 节 |
|
|
120
|
-
| `initialState.tools` | 可选,pi 的 `AgentTool` 数组 |
|
|
121
|
-
| `initialState.thinkingLevel` | 可选,默认 `off`。模型不支持的级别在请求时就近换成支持的一档,建会话时打一条 warn |
|
|
122
|
-
| `platformTools` | 可选,给模型注册哪些平台工具,默认 `[]`,见第 3 节 |
|
|
123
|
-
| `proactiveTools` | 可选,主动回合里还能用哪些工具,默认 `[]`(一个都不给)。写工具名,`platformTools` 选中的和 `initialState.tools` 里自己的都行;写了没注册的名字建会话时抛异常。见 3.6 |
|
|
124
|
-
| 其余字段 | 原样交给 pi;`beforeToolCall` 抛异常时按放行处理 |
|
|
137
|
+
// 可选。模型出错或一句话没说时发的兜底回复,用户和模型之后都看得到;主动回合不发
|
|
138
|
+
errorReply?: string;
|
|
125
139
|
|
|
126
|
-
|
|
140
|
+
// 可选。工具执行前调用,context 里有这次的 toolCall、校验过的 args 和当前上下文。
|
|
141
|
+
// 返回 { block: true, reason } 时工具不执行,模型看到 reason;抛异常时工具也不执行,模型看到异常信息
|
|
142
|
+
beforeToolCall?: (context: BeforeToolCallContext, signal?: AbortSignal) => Promise<BeforeToolCallResult | undefined>;
|
|
127
143
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
- 先单独声明为 `AgentTool<typeof 参数 schema>` 再放进数组,`execute` 的参数才有类型;直接写在数组里时参数类型是 `unknown`。
|
|
131
|
-
- 工具名不能与平台工具或其他工具重复。
|
|
144
|
+
// 可选。工具执行完调用,context 里带着执行结果;返回的 content、details、isError 会替换结果里的同名字段,抛异常时结果换成异常信息
|
|
145
|
+
afterToolCall?: (context: AfterToolCallContext, signal?: AbortSignal) => Promise<AfterToolCallResult | undefined>;
|
|
132
146
|
|
|
133
|
-
|
|
147
|
+
// 可选。模型说完一步(这一步的工具也跑完)后调用,返回 true 就不再请求模型。
|
|
148
|
+
// 这一步发出了权限请求卡或题卡时,这一轮直接停下,不调它
|
|
149
|
+
shouldStopAfterTurn?: (context: ShouldStopAfterTurnContext, signal?: AbortSignal) => boolean | Promise<boolean>;
|
|
150
|
+
}
|
|
151
|
+
```
|
|
134
152
|
|
|
135
153
|
### 2.3 `Conversation`
|
|
136
154
|
|
|
@@ -144,8 +162,23 @@ interface Conversation {
|
|
|
144
162
|
// 日志,每行开头带搭子 id
|
|
145
163
|
log: BuddyLogger;
|
|
146
164
|
|
|
165
|
+
// 平台工具,见第 3 节:运行时造好、绑好这场会话,挑要用的放进 initialState.tools
|
|
166
|
+
tools: {
|
|
167
|
+
resource_list: AgentTool;
|
|
168
|
+
resource_read: AgentTool;
|
|
169
|
+
ask_question: AgentTool;
|
|
170
|
+
data_access_query: AgentTool;
|
|
171
|
+
data_access_request: AgentTool;
|
|
172
|
+
data_access_read_result: AgentTool;
|
|
173
|
+
widget_create: AgentTool;
|
|
174
|
+
widget_write: AgentTool;
|
|
175
|
+
widget_read: AgentTool;
|
|
176
|
+
widget_delete: AgentTool;
|
|
177
|
+
widget_send: AgentTool;
|
|
178
|
+
};
|
|
179
|
+
|
|
147
180
|
// 资料,见 3.1:resources/ 里的 .md,进程启动时读好,所有会话共用
|
|
148
|
-
|
|
181
|
+
resource: {
|
|
149
182
|
list(): string[];
|
|
150
183
|
read(file: string): string;
|
|
151
184
|
};
|
|
@@ -160,10 +193,12 @@ interface Conversation {
|
|
|
160
193
|
// 授权数据,见 3.4:私聊不传 memberId,群聊传消息前缀里的 m_…
|
|
161
194
|
dataAccess: {
|
|
162
195
|
query(request: { datasets: string[]; memberId?: string }): Promise<DatasetAccessResult[]>;
|
|
196
|
+
request(input: DataAccessRequestInput & { idempotencyKey: string }): Promise<void>;
|
|
197
|
+
readResult(requestId: string): Promise<DataAccessReadResult>;
|
|
163
198
|
};
|
|
164
199
|
|
|
165
200
|
// 小挂件,见 3.5
|
|
166
|
-
|
|
201
|
+
widget: {
|
|
167
202
|
list(): Promise<WidgetSummary[]>;
|
|
168
203
|
create(input: { type: string; title: string; idempotencyKey?: string }): Promise<{ id: string }>;
|
|
169
204
|
read(id: string): Promise<Widget>;
|
|
@@ -179,34 +214,44 @@ interface Conversation {
|
|
|
179
214
|
send(id: string, options: { idempotencyKey: string }): void;
|
|
180
215
|
};
|
|
181
216
|
|
|
182
|
-
// 主动服务,见 3.6
|
|
217
|
+
// 主动服务,见 3.6:propose 提订阅建议,用户在 App 里确认后才生效;isRunning 判断现在是不是主动回合
|
|
183
218
|
proactive: {
|
|
184
219
|
propose(request: ProactiveSubscriptionRequest): Promise<{ id: string; status: "pending" }>;
|
|
220
|
+
isRunning(): boolean;
|
|
185
221
|
};
|
|
186
222
|
}
|
|
187
223
|
```
|
|
188
224
|
|
|
189
|
-
`memory`、`dataAccess`、`
|
|
225
|
+
`tools`、`memory`、`dataAccess`、`widget`、`proactive` 已经绑好这场会话,直接用,不用传令牌或拼接口路径。
|
|
190
226
|
|
|
191
227
|
## 3. 平台能力
|
|
192
228
|
|
|
193
|
-
| 能力 | `conversation` 对象方法 |
|
|
194
|
-
|
|
195
|
-
| 资料 | `conversation.
|
|
196
|
-
| 提问 | 无 | `ask_question` |
|
|
197
|
-
| 记忆 | `conversation.memory` |
|
|
198
|
-
|
|
|
199
|
-
|
|
|
200
|
-
|
|
|
201
|
-
|
|
229
|
+
| 能力 | `conversation` 对象方法 | 模型工具(`conversation.tools` 上) |
|
|
230
|
+
|---|---|---|
|
|
231
|
+
| 资料 | `conversation.resource.list` / `conversation.resource.read` | `resource_list` / `resource_read` |
|
|
232
|
+
| 提问 | 无 | `ask_question` |
|
|
233
|
+
| 记忆 | `conversation.memory` | 无,开发者可以根据具体场景,自己组装成适合的模型工具 |
|
|
234
|
+
| 用户授权数据 | `conversation.dataAccess.query` / `conversation.dataAccess.request` / `conversation.dataAccess.readResult` | `data_access_query` / `data_access_request` / `data_access_read_result` |
|
|
235
|
+
| 小挂件 | `conversation.widget.list` / `conversation.widget.create` / `conversation.widget.read` / `conversation.widget.readModule` / `conversation.widget.rename` / `conversation.widget.write` / `conversation.widget.deleteRecord` / `conversation.widget.delete` / `conversation.widget.setPresentation` / `conversation.widget.send` | `widget_create` / `widget_write` / `widget_read` / `widget_delete` / `widget_send` |
|
|
236
|
+
| 主动服务 | `conversation.proactive.propose` / `conversation.proactive.isRunning` | 无 |
|
|
237
|
+
|
|
238
|
+
```ts
|
|
239
|
+
buddy: (conversation) => ({
|
|
240
|
+
initialState: {
|
|
241
|
+
model: "kimi-k3",
|
|
242
|
+
systemPrompt: PERSONA,
|
|
243
|
+
tools: [conversation.tools.resource_list, conversation.tools.resource_read, conversation.tools.ask_question, ...myTools],
|
|
244
|
+
},
|
|
245
|
+
}),
|
|
246
|
+
```
|
|
202
247
|
|
|
203
248
|
### 3.1 资料
|
|
204
249
|
|
|
205
|
-
把 `.md` 资料放进工程目录(`package.json` 所在目录)下的 `resources
|
|
250
|
+
把 `.md` 资料放进工程目录(`package.json` 所在目录)下的 `resources/`,把 `conversation.tools.resource_list` 和 `conversation.tools.resource_read` 放进 `initialState.tools`,模型先列出有哪些资料,再按需读。
|
|
206
251
|
|
|
207
252
|
- 工程目录从入口脚本往上找,与启动时的工作目录无关。
|
|
208
253
|
- 启动时递归读取 `.md`,跳过 `node_modules`、`venv`、`.venv`、`__pycache__` 与点开头的文件和目录;资料改了要重启进程。
|
|
209
|
-
-
|
|
254
|
+
- `resource_list` 列出全部资料的相对路径,`resource_read` 按路径读一篇。没有资料时 `resource_list` 回「没有资料」。
|
|
210
255
|
|
|
211
256
|
```
|
|
212
257
|
my-buddy/ 工程目录
|
|
@@ -219,7 +264,7 @@ my-buddy/ 工程目录
|
|
|
219
264
|
|
|
220
265
|
**在自己的代码里用**
|
|
221
266
|
|
|
222
|
-
调用 `conversation.
|
|
267
|
+
调用 `conversation.resource`,不需要把这两个工具放进 `initialState.tools`:
|
|
223
268
|
|
|
224
269
|
| 方法 | 做什么 |
|
|
225
270
|
|---|---|
|
|
@@ -227,7 +272,7 @@ my-buddy/ 工程目录
|
|
|
227
272
|
| `read(file)` | 读一篇的正文,`file` 照 `list()` 给的路径原样填;没有这篇时抛异常 |
|
|
228
273
|
|
|
229
274
|
```ts
|
|
230
|
-
const notes = conversation.
|
|
275
|
+
const notes = conversation.resource.read("research/01-writings.md");
|
|
231
276
|
```
|
|
232
277
|
|
|
233
278
|
- 两个方法都是同步的,读的是进程启动时读好的那份。
|
|
@@ -239,7 +284,7 @@ const notes = conversation.resources.read("research/01-writings.md");
|
|
|
239
284
|
|
|
240
285
|
让模型发一张题卡请用户选:一个问题加 2 到 4 个可以直接点的选项。私聊、群聊都能用。
|
|
241
286
|
|
|
242
|
-
把 `ask_question`
|
|
287
|
+
把 `conversation.tools.ask_question` 放进 `initialState.tools`,模型就能发题卡。
|
|
243
288
|
|
|
244
289
|
- 参数是 `question`(题目,不超过 500 字)和 `options`(2 到 4 个,每个不超过 30 字,不能重复);不合规时模型会看到错误,改了再调。
|
|
245
290
|
- 用户点选项,就是用选项原文发一条消息,跟自己打字一样;也可以不点,直接打字。
|
|
@@ -271,7 +316,6 @@ interface MemoryEntry {
|
|
|
271
316
|
const REMEMBER_PARAMS = Type.Object({ topic: Type.String(), text: Type.String() });
|
|
272
317
|
|
|
273
318
|
await BuddyServer.start({
|
|
274
|
-
model: "kimi-k3",
|
|
275
319
|
dataRequirements: [],
|
|
276
320
|
buddy: (conversation) => {
|
|
277
321
|
const remember: AgentTool<typeof REMEMBER_PARAMS, undefined> = {
|
|
@@ -284,58 +328,51 @@ await BuddyServer.start({
|
|
|
284
328
|
return { content: [{ type: "text", text: "记下了。" }], details: undefined };
|
|
285
329
|
},
|
|
286
330
|
};
|
|
287
|
-
return { initialState: { systemPrompt: PERSONA, tools: [remember] } };
|
|
331
|
+
return { initialState: { model: "kimi-k3", systemPrompt: PERSONA, tools: [remember] } };
|
|
288
332
|
},
|
|
289
333
|
});
|
|
290
334
|
```
|
|
291
335
|
|
|
292
|
-
### 3.4
|
|
336
|
+
### 3.4 用户授权数据
|
|
293
337
|
|
|
294
338
|
用户同意后,搭子可以读用户的睡眠、运动等数据。数据按 Dataset 分项,用户逐项同意。搭子拿不到用户的真实 id。
|
|
295
339
|
|
|
296
340
|
这里有三层不同概念:
|
|
297
341
|
|
|
298
|
-
1.
|
|
342
|
+
1. **Runtime 能力目录**:当前 SDK 支持哪些 Dataset,由包导出的 `DATA_ACCESS_CAPABILITIES` 提供。
|
|
299
343
|
2. **搭子数据声明**:这只搭子实际需要其中哪些 Dataset,以及各自用途,写在 `BuddyServer.start.dataRequirements`。
|
|
300
344
|
3. **用户 Grant**:用户是否同意这只搭子读取某个已声明 Dataset,由客户端授权流程管理,搭子不能代替用户授予。
|
|
301
345
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
位置和日历是当前会话的一次性权限结果,通过 `permission_request` 请求,不属于 Dataset,也不写进 `dataRequirements`。HealthKit 数据会形成可重复查询的 Dataset,因此必须先在 `dataRequirements` 中声明。
|
|
346
|
+
把 `data_access_query`、`data_access_request` 等平台工具放进 `initialState.tools`,只决定模型能不能调用它们,不等于声明 Dataset,也不等于获得用户授权。
|
|
305
347
|
|
|
306
|
-
|
|
348
|
+
位置和日历是当前会话的一次性授权结果,通过 `data_access_request` 请求,不属于 Dataset,也不写进 `dataRequirements`。HealthKit 数据会形成可重复查询的 Dataset,因此必须先在 `dataRequirements` 中声明。
|
|
307
349
|
|
|
308
|
-
|
|
350
|
+
**读取 Runtime 能力目录**
|
|
309
351
|
|
|
310
|
-
|
|
311
|
-
GET /cli/data-access/capabilities
|
|
312
|
-
Authorization: Bearer <用户 token>
|
|
313
|
-
```
|
|
352
|
+
开发者工具和项目代码直接从 Runtime 包导入,不需要网络请求:
|
|
314
353
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
"schemaVersion": 1
|
|
325
|
-
}
|
|
326
|
-
]
|
|
354
|
+
```ts
|
|
355
|
+
import {
|
|
356
|
+
DATA_ACCESS_CAPABILITIES,
|
|
357
|
+
dataAccessCapability,
|
|
358
|
+
type DataAccessDatasetId,
|
|
359
|
+
} from "@my-life-buddies/buddy-runtime";
|
|
360
|
+
|
|
361
|
+
for (const capability of DATA_ACCESS_CAPABILITIES) {
|
|
362
|
+
console.log(capability.id, capability.title, capability.source, capability.schemaVersion);
|
|
327
363
|
}
|
|
364
|
+
|
|
365
|
+
const sleep = dataAccessCapability("health.sleep");
|
|
328
366
|
```
|
|
329
367
|
|
|
330
|
-
|
|
368
|
+
这份导出是开发者可选 `id` 的权威来源。新增 Dataset 随 Runtime 版本发布;developer-platform、脚手架和预检都应读取该导出,不再维护一份手写枚举。
|
|
331
369
|
|
|
332
370
|
**声明需要哪些数据**
|
|
333
371
|
|
|
334
|
-
声明写在 `BuddyServer.start` 的初始化参数里,Runtime
|
|
372
|
+
声明写在 `BuddyServer.start` 的初始化参数里,Runtime 在本地展开成带标题、来源和 Schema 版本的完整声明:
|
|
335
373
|
|
|
336
374
|
```ts
|
|
337
375
|
await BuddyServer.start({
|
|
338
|
-
model: "kimi-k3",
|
|
339
376
|
dataRequirements: [
|
|
340
377
|
{ id: "health.sleep", purpose: "根据你最近的睡眠调整作息建议" },
|
|
341
378
|
],
|
|
@@ -343,13 +380,11 @@ await BuddyServer.start({
|
|
|
343
380
|
});
|
|
344
381
|
```
|
|
345
382
|
|
|
346
|
-
- `id
|
|
383
|
+
- `id`:从 `DATA_ACCESS_CAPABILITIES` 中选,TypeScript 会收窄为 `DataAccessDatasetId`。
|
|
347
384
|
- `purpose`:用途,1~200 字。用户看到这句话再决定是否同意。
|
|
348
|
-
-
|
|
349
|
-
-
|
|
350
|
-
-
|
|
351
|
-
- 本地进程连接线上网关时使用 `<buddyId>-dev` 身份,其声明和用户授权与正式搭子隔离,不会覆盖线上版本。
|
|
352
|
-
- 可用 `GET /cli/buddies/:id/data-requirements` 查看某搭子当前已经上传并生效的声明;这个接口只读。
|
|
385
|
+
- Runtime 不在启动时注册声明;查询、HealthKit 权限卡和主动服务请求会携带当前完整声明。
|
|
386
|
+
- Server 在实际请求时按声明用途和 Schema 计算版本;`id`、`purpose` 与 Schema 都没变化时保留已有 Grant,变化后要求重新授权。
|
|
387
|
+
- 本地进程连接线上网关时使用 `<buddyId>-dev` 身份,实际使用形成的授权上下文与正式搭子隔离。
|
|
353
388
|
|
|
354
389
|
| Dataset | 内容 | `data.items` 每条的字段 |
|
|
355
390
|
|---|---|---|
|
|
@@ -364,11 +399,65 @@ await BuddyServer.start({
|
|
|
364
399
|
|
|
365
400
|
部分字段可能缺失,取值前先判断。
|
|
366
401
|
|
|
367
|
-
|
|
402
|
+
**模型工具与代码接口**
|
|
403
|
+
|
|
404
|
+
- `data_access_query` / `conversation.dataAccess.query`:查询已授权 Dataset。
|
|
405
|
+
- `data_access_request` / `conversation.dataAccess.request`:在私聊中发数据授权卡;发卡后当前回合结束,等待用户操作。
|
|
406
|
+
- `data_access_read_result` / `conversation.dataAccess.readResult`:按授权卡的 `requestId` 读取一次性位置或日历结果;HealthKit 不走这个接口。
|
|
368
407
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
408
|
+
模型工具要从 `conversation.tools` 取来放进 `initialState.tools` 才能调用;没声明 Dataset 时照常注册,查询的每一项都回 `notDeclared`。开发者自己的工具直接调用 `conversation.dataAccess`,不用把这几个工具放进去。
|
|
409
|
+
|
|
410
|
+
**HealthKit:先查询,缺授权再发卡**
|
|
411
|
+
|
|
412
|
+
```ts
|
|
413
|
+
await BuddyServer.start({
|
|
414
|
+
dataRequirements: [
|
|
415
|
+
{ id: "health.sleep", purpose: "根据你最近的睡眠调整作息建议" },
|
|
416
|
+
],
|
|
417
|
+
buddy: (conversation) => ({
|
|
418
|
+
initialState: {
|
|
419
|
+
model: "kimi-k3",
|
|
420
|
+
systemPrompt: [
|
|
421
|
+
"聊到作息时,先用 data_access_query 查询 health.sleep。",
|
|
422
|
+
"返回 notGranted 时,说明当前问题为什么需要数据,再用 data_access_request 发 HealthKit 授权卡。",
|
|
423
|
+
"用户处理授权卡后会开启新回合;再次调用 data_access_query,不要调用 data_access_read_result。",
|
|
424
|
+
"用户拒绝后不要在同一任务里反复申请。",
|
|
425
|
+
].join("\n"),
|
|
426
|
+
tools: [conversation.tools.data_access_query, conversation.tools.data_access_request],
|
|
427
|
+
},
|
|
428
|
+
}),
|
|
429
|
+
});
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
完整链路:
|
|
433
|
+
|
|
434
|
+
```text
|
|
435
|
+
data_access_query
|
|
436
|
+
→ notGranted
|
|
437
|
+
→ data_access_request { kind: "healthkit", datasets: ["health.sleep"], reason: "..." }
|
|
438
|
+
→ 用户在授权卡操作,平台更新 Grant 和 Snapshot
|
|
439
|
+
→ 隐藏结果开启新回合
|
|
440
|
+
→ data_access_query
|
|
441
|
+
→ available + data
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
查询接口不会把“从未授权”和“用途变化后需重新确认”分成两种模型状态,都会返回 `notGranted`;重新确认原因由用户侧授权页面展示。
|
|
445
|
+
|
|
446
|
+
**位置和日历:读取一次性结果**
|
|
447
|
+
|
|
448
|
+
位置和日历不属于 Dataset,不写进 `dataRequirements`。模型先用 `data_access_request` 发卡,用户处理后,隐藏结果会带回这张卡的 `requestId`;再用 `data_access_read_result` 读取。结果只在平台短期保存,读不到时返回 `unavailable`。
|
|
449
|
+
|
|
450
|
+
```text
|
|
451
|
+
data_access_request { kind: "location", reason: "查找你附近的地点" }
|
|
452
|
+
→ 用户在授权卡操作
|
|
453
|
+
→ 隐藏结果开启新回合并带回 requestId
|
|
454
|
+
→ data_access_read_result { requestId }
|
|
455
|
+
→ available + data,或 unavailable
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
`calendar` 还可传 `calendarDays`,范围为 1~31,默认 7。授权请求第一期只支持私聊;群聊需要引导目标成员去私聊操作。
|
|
459
|
+
|
|
460
|
+
**开发者代码查询 Dataset**
|
|
372
461
|
|
|
373
462
|
```ts
|
|
374
463
|
const [sleep] = await conversation.dataAccess.query({ datasets: ["health.sleep"] });
|
|
@@ -403,7 +492,7 @@ if (sleep.status === "available") {
|
|
|
403
492
|
|
|
404
493
|
### 3.5 小挂件
|
|
405
494
|
|
|
406
|
-
小挂件是会话里一块给用户看的结构化面板,比如一份菜单。模型通过平台工具、开发者通过 `conversation.
|
|
495
|
+
小挂件是会话里一块给用户看的结构化面板,比如一份菜单。模型通过平台工具、开发者通过 `conversation.widget`,都能建小挂件、写记录、读记录、删记录,并把它发进聊天。
|
|
407
496
|
|
|
408
497
|
**放进工程目录**
|
|
409
498
|
|
|
@@ -421,22 +510,22 @@ my-buddy/ 工程目录
|
|
|
421
510
|
|
|
422
511
|
**让模型使用**
|
|
423
512
|
|
|
424
|
-
把要用的 `widget_*`
|
|
513
|
+
把要用的 `conversation.tools.widget_*` 放进 `initialState.tools`。
|
|
425
514
|
|
|
426
515
|
- 工具说明里附上各类型的模块与字段。改了 `widgets/` 要重启进程。
|
|
427
|
-
-
|
|
516
|
+
- 这场会话里已有哪些小挂件,平台会在消息里告诉模型。
|
|
428
517
|
- 平台请求失败时,模型只看到「平台请求失败(HTTP 409),请查询最新状态后再操作」这类提示,不带具体原因。
|
|
429
518
|
- `widget_send` 把小挂件放进发送队列就返回,不等平台确认送达。
|
|
430
519
|
|
|
431
520
|
**在自己的代码里用**
|
|
432
521
|
|
|
433
|
-
调用 `conversation.
|
|
522
|
+
调用 `conversation.widget`,不需要把 `widget_*` 放进工具:
|
|
434
523
|
|
|
435
524
|
```ts
|
|
436
525
|
execute: async (toolCallId, params) => {
|
|
437
|
-
const { id } = await conversation.
|
|
438
|
-
await conversation.
|
|
439
|
-
conversation.
|
|
526
|
+
const { id } = await conversation.widget.create({ type: "menu", title: "本周菜单", idempotencyKey: `menu:${toolCallId}` });
|
|
527
|
+
await conversation.widget.write(id, { module: "菜单", recordId: "2026-09-14", data: { dishes: ["红烧肉", "清炒时蔬"] } });
|
|
528
|
+
conversation.widget.send(id, { idempotencyKey: `menu-send:${toolCallId}` });
|
|
440
529
|
return { content: [{ type: "text", text: "菜单建好了。" }], details: undefined };
|
|
441
530
|
},
|
|
442
531
|
```
|
|
@@ -496,8 +585,17 @@ await conversation.proactive.propose({
|
|
|
496
585
|
|
|
497
586
|
主动回合里:
|
|
498
587
|
|
|
499
|
-
-
|
|
500
|
-
-
|
|
501
|
-
|
|
588
|
+
- **工具清单和普通回合一样,运行时不按工具名拦。** `widget_create`、`widget_write`、`widget_delete`、`widget_send`、`ask_question`、`data_access_request`、`data_access_read_result` 调了会抛异常,模型看到错误;`data_access_query` 只能读订阅的 `allowedDataScopes`。
|
|
589
|
+
- **开发者自己的工具,运行时不拦。** 用户不在场,工具里要是会发请求、写外部系统,就在 `beforeToolCall` 里按 `conversation.proactive.isRunning()` 挡掉:
|
|
590
|
+
|
|
591
|
+
```ts
|
|
592
|
+
beforeToolCall: async ({ toolCall }) =>
|
|
593
|
+
conversation.proactive.isRunning() && toolCall.name !== "read_plan"
|
|
594
|
+
? { block: true, reason: "这一轮用户不在场,这个工具用不了。" }
|
|
595
|
+
: undefined,
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
- 系统提示和普通回合一字不差,记忆不会被附上去。要让模型用记忆,自己把 `conversation.memory.list` 包成工具放进 `initialState.tools`。
|
|
599
|
+
- 调用 `memory.write`、`memory.delete`、`proactive.propose`,以及 `widget` 上除 `list`、`read`、`readModule` 以外的方法,会抛异常;开发者的钩子照常执行。
|
|
502
600
|
- 用户这时发来消息,主动回合中止,先回复用户;超过 4 分钟也中止。
|
|
503
|
-
- 主动回合的过程不下发给用户:平台在会话流里记两条不可见的记录(叫你判断了什么、你判断的结果),中间的工具步照常写回但也不下发。**开口的那条消息才是用户看得到的**,之后作为搭子说过的话出现在会话历史里。
|
|
601
|
+
- 主动回合的过程不下发给用户:平台在会话流里记两条不可见的记录(叫你判断了什么、你判断的结果),中间的工具步照常写回但也不下发。**开口的那条消息才是用户看得到的**,之后作为搭子说过的话出现在会话历史里。
|
|
@@ -14,13 +14,14 @@ export interface AgentDeps {
|
|
|
14
14
|
key: string;
|
|
15
15
|
buddyId: string;
|
|
16
16
|
client: MLBClient;
|
|
17
|
-
model
|
|
17
|
+
/** 平台网关地址:按开发者的 initialState.model 造模型条目时,baseUrl 指到它的 /internal/v1 */
|
|
18
|
+
gatewayUrl: string;
|
|
18
19
|
streamFn: StreamFn;
|
|
19
20
|
/** 搭子令牌:经 Agent 的 getApiKey 交给模型通道,变成 Authorization: Bearer 头 */
|
|
20
21
|
token: string;
|
|
21
|
-
/** 小挂件类型目录(server
|
|
22
|
+
/** 小挂件类型目录(server 启动时上传平台的那些);空数组时 widget_* 照常注册,说明里的类型目录为空 */
|
|
22
23
|
widgetTypes: WidgetType[];
|
|
23
|
-
/** 搭子目录下 resources/
|
|
24
|
+
/** 搭子目录下 resources/ 里的资料;空数组时 resource_list 回「没有资料」 */
|
|
24
25
|
resources: Resource[];
|
|
25
26
|
dataRequirements: readonly DataRequirement[];
|
|
26
27
|
factory: BuddyFactory;
|
|
@@ -28,11 +29,16 @@ export interface AgentDeps {
|
|
|
28
29
|
/** Agent 事件的去处。pi 会等它返回,所以只能同步做事 */
|
|
29
30
|
onEvent: (agent: Agent, event: AgentEvent) => void;
|
|
30
31
|
}
|
|
31
|
-
/**
|
|
32
|
+
/** createAgent 建好的:Agent、这场会话的模型条目、授权数据记账,以及开发者配的兜底话 */
|
|
32
33
|
export interface CreatedAgent {
|
|
33
34
|
agent: Agent;
|
|
35
|
+
/** 按开发者 initialState.model 造的模型条目:重建历史时抄身份、判断收不收图片都按它 */
|
|
36
|
+
model: Model<Api>;
|
|
34
37
|
dataAccessUsage: DataAccessUsageTracker;
|
|
38
|
+
/** BuddyOptions.errorReply:普通回合出错或一句话没说时由 index.ts 的 runTurn 发;没配是 undefined */
|
|
39
|
+
errorReply: string | undefined;
|
|
35
40
|
}
|
|
41
|
+
/** 跑开发者工厂拿配置,叠上运行时预置,建 Agent。模型 id 运行时不支持、工具清单重名、拉平台 system prompt 失败 → throw。 */
|
|
36
42
|
export declare function createAgent(deps: AgentDeps): Promise<CreatedAgent>;
|
|
37
43
|
/**
|
|
38
44
|
* 每次请求模型时接在历史末尾那条 user 消息的正文;现在的时间由 convertToLlm 按这条消息的 timestamp 拼在开头。
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/conversation/agent.ts"],"names":[],"mappings":"
|
|
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;AAIrD,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,gFAAgF;IAChF,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;CAC/B;AAED,kFAAkF;AAClF,wBAAsB,WAAW,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,YAAY,CAAC,CA2LxE;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,0DAA0D;AAC1D,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAUxD"}
|