@my-life-buddies/buddy-runtime 0.28.0 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +146 -120
  3. package/dist/conversation/agent.d.ts +11 -3
  4. package/dist/conversation/agent.d.ts.map +1 -1
  5. package/dist/conversation/agent.js +16 -5
  6. package/dist/conversation/agent.js.map +1 -1
  7. package/dist/conversation/compaction.js +1 -1
  8. package/dist/conversation/compaction.js.map +1 -1
  9. package/dist/conversation/image.d.ts +7 -0
  10. package/dist/conversation/image.d.ts.map +1 -0
  11. package/dist/conversation/image.js +36 -0
  12. package/dist/conversation/image.js.map +1 -0
  13. package/dist/conversation/index.d.ts +4 -0
  14. package/dist/conversation/index.d.ts.map +1 -1
  15. package/dist/conversation/index.js +23 -4
  16. package/dist/conversation/index.js.map +1 -1
  17. package/dist/conversation/writeMLBMessage.d.ts +8 -0
  18. package/dist/conversation/writeMLBMessage.d.ts.map +1 -1
  19. package/dist/conversation/writeMLBMessage.js +29 -3
  20. package/dist/conversation/writeMLBMessage.js.map +1 -1
  21. package/dist/dataAccessCatalog.d.ts +5 -0
  22. package/dist/dataAccessCatalog.d.ts.map +1 -1
  23. package/dist/dataAccessCatalog.js +1 -0
  24. package/dist/dataAccessCatalog.js.map +1 -1
  25. package/dist/index.d.ts +1 -1
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/mlbClient.d.ts +6 -1
  28. package/dist/mlbClient.d.ts.map +1 -1
  29. package/dist/mlbClient.js +50 -4
  30. package/dist/mlbClient.js.map +1 -1
  31. package/dist/model.d.ts +8 -6
  32. package/dist/model.d.ts.map +1 -1
  33. package/dist/model.js +34 -13
  34. package/dist/model.js.map +1 -1
  35. package/dist/tools/dataAccess.d.ts +7 -0
  36. package/dist/tools/dataAccess.d.ts.map +1 -1
  37. package/dist/tools/dataAccess.js +20 -2
  38. package/dist/tools/dataAccess.js.map +1 -1
  39. package/dist/tools/dataAccessRequest.d.ts.map +1 -1
  40. package/dist/tools/dataAccessRequest.js +8 -4
  41. package/dist/tools/dataAccessRequest.js.map +1 -1
  42. package/dist/tools/generateImage.d.ts +16 -0
  43. package/dist/tools/generateImage.d.ts.map +1 -0
  44. package/dist/tools/generateImage.js +40 -0
  45. package/dist/tools/generateImage.js.map +1 -0
  46. package/dist/tools/profileDisclosure.d.ts +13 -0
  47. package/dist/tools/profileDisclosure.d.ts.map +1 -0
  48. package/dist/tools/profileDisclosure.js +154 -0
  49. package/dist/tools/profileDisclosure.js.map +1 -0
  50. package/dist/tools/sendImage.d.ts +6 -10
  51. package/dist/tools/sendImage.d.ts.map +1 -1
  52. package/dist/tools/sendImage.js +24 -15
  53. package/dist/tools/sendImage.js.map +1 -1
  54. package/dist/tools/viewImage.d.ts.map +1 -1
  55. package/dist/tools/viewImage.js +3 -2
  56. package/dist/tools/viewImage.js.map +1 -1
  57. package/dist/types.d.ts +53 -11
  58. package/dist/types.d.ts.map +1 -1
  59. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -8,6 +8,37 @@
8
8
 
9
9
  0.23.0 及更早的版本未记录。
10
10
 
11
+ ## 0.30.0(2026-10-27)
12
+
13
+ 不兼容改动:有
14
+
15
+ ### 不兼容改动
16
+
17
+ - `ModelId` 移除 `deepseek-v4-flash`、`deepseek-v4-pro` 和 `glm-5.3`。`initialState.model` 或 `configureTurn` 返回的 `model` 使用这三个值时,开发者需改为 `kimi-k2.6`、`kimi-k3`、`deepseek-v4.1-flash-1017` 或 `MiniMax-M3`;否则类型检查失败,创建会话时也会报错。
18
+ - 已发布且内含旧版 Runtime 的搭子产物不会随 npm 发版自动升级;只有升级 Runtime 的工程需要调整上述配置。
19
+
20
+ ### 新增
21
+
22
+ - 新增主聊模型 `deepseek-v4.1-flash-1017`,支持文字与图片输入,上下文窗口为 1,048,576 tokens,输出上限为 384,000 tokens。
23
+ - 当前四个可选主聊模型均支持图片输入,并提供运行时内置的 `view_image` 工具读取历史图片。
24
+
25
+ ## 0.29.0(2026-10-26)
26
+
27
+ 不兼容改动:有
28
+
29
+ ### 不兼容改动
30
+
31
+ - 内置 `data_access_query` 查询 `xhs_user_behaviour_data` 时,由返回完整 `data.memory` 改为先返回 `data.view="index"` 和片段目录,再通过 `profileRead: { version, sectionId }` 读取正文。依赖该工具原始结果的代码需适配目录与片段;`conversation.dataAccess.query` 的原始 Dataset 契约保持不变。
32
+ - 画像工具对未知 JSON 封装不再按纯文本返回;旧纯文本和 XML 继续按大小分片。
33
+
34
+ ### 新增
35
+
36
+ - 新增可选 `initialState.imageModel`、`conversation.image.generate`、`generateAndSend` 及对应模型工具,支持生成图片和使用参考图片改图;生图请求不会自动重试。
37
+ - `image.send` 和 `send_image` 支持通过 `mediaId` 发送已存图片,等待消息保存后返回 `messageId`。原有按字节、文件和 URL 发图的调用方式及完成标准保留。
38
+ - 新增小红书资料与行为画像 Dataset,以及 `platformAuthorizationRequired`、`bindingRequired` 授权状态和绑定授权卡,需要平台支持。
39
+ - 画像按目录和片段读取,每段最多 4 KiB,每回合目录及片段 JSON 合计最多 24 KiB;主题目录每页最多 8 项,可通过 `profileOffset` 翻页。画像版本变化时重新返回目录,读取过程持续校验授权。
40
+ - 新增可选 `beforeReply`,在需要回复的用户消息交给模型前执行;返回 `true` 表示已由开发者处理,本次不调用模型。
41
+
11
42
  ## 0.28.0(2026-10-19)
12
43
 
13
44
  不兼容改动:无(相对正式版 0.27.0)
package/README.md CHANGED
@@ -110,9 +110,12 @@ import type {
110
110
  interface BuddyOptions {
111
111
  // 必填。这场会话的初始状态
112
112
  initialState: {
113
- // 必填。模型 id,ModelId 是 "kimi-k2.6" | "kimi-k3" | "deepseek-v4-flash" | "deepseek-v4-pro" | "glm-5.3" | "MiniMax-M3"。每场会话可以选不同的模型
113
+ // 必填。模型 id,ModelId 是 "kimi-k2.6" | "kimi-k3" | "deepseek-v4.1-flash-1017" | "MiniMax-M3",都支持图片输入。每场会话可以选不同的模型
114
114
  model: ModelId;
115
115
 
116
+ // 可选。独立的生图模型,如 "Doubao-Seedream-5.0-lite";调用生图方法或选用两个生图工具时必填
117
+ imageModel?: string;
118
+
116
119
  // 必填。人设与做事规则。为保证模型缓存,只放不随回合变化的内容
117
120
  systemPrompt: string;
118
121
 
@@ -127,6 +130,10 @@ interface BuddyOptions {
127
130
  // 可选。模型出错或一句话没说时发的兜底回复,用户和模型之后都看得到;主动服务的回合不发
128
131
  errorReply?: string;
129
132
 
133
+ // 可选。每条需要回复的用户消息送模型前调用,可按消息判断是否需要数据。返回 true 表示已自行处理(例如发授权卡),本轮不调用模型。
134
+ // 适合对必须先授权的数据做确定性检查,不依赖模型主动调用工具。
135
+ beforeReply?: (messages: readonly AgentMessage[]) => boolean | void | Promise<boolean | void>;
136
+
130
137
  // 可选。工具执行前调用,context 里有这次的 toolCall、校验过的 args 和当前上下文。
131
138
  // 返回 { block: true, reason } 时工具不执行,模型看到 reason;抛异常时工具也不执行,模型看到异常信息
132
139
  beforeToolCall?: (context: BeforeToolCallContext, signal?: AbortSignal) => Promise<BeforeToolCallResult | undefined>;
@@ -142,84 +149,23 @@ interface BuddyOptions {
142
149
 
143
150
  ### 2.3 `Conversation`
144
151
 
145
- `buddy` 拿到的参数,工具闭包里也能用。
146
-
147
- ```ts
148
- interface Conversation {
149
- // 会话键:私聊以 p_ 开头,群聊以 g_ 开头。开发者自己存的数据要按它隔离
150
- key: string;
151
-
152
- // 日志,每行开头带搭子 id
153
- log: BuddyLogger;
154
-
155
- // 平台工具,见第 3 节:运行时造好、绑好这场会话,挑要用的放进 initialState.tools
156
- tools: {
157
- resource_list: AgentTool;
158
- resource_read: AgentTool;
159
- ask_question: AgentTool;
160
- data_access_query: AgentTool;
161
- data_access_request: AgentTool;
162
- buddy_relationship_request?: AgentTool;
163
- widget_create: AgentTool;
164
- widget_read: AgentTool;
165
- widget_read_module: AgentTool;
166
- widget_rename: AgentTool;
167
- widget_write_record: AgentTool;
168
- widget_delete_record: AgentTool;
169
- widget_send: AgentTool;
170
- send_image: AgentTool;
171
- };
172
-
173
- // 资料,见 3.1:resources/ 里的 .md,进程启动时读好,所有会话共用
174
- resource: {
175
- list(): string[];
176
- read(file: string): string;
177
- };
178
-
179
- // 发图,见 3.4:把开发者自己拿到的图片字节发进聊天流
180
- image: {
181
- send(input: { bytes: Uint8Array; mimeType: "image/jpeg" | "image/png" | "image/webp"; text?: string; idempotencyKey: string }): Promise<void>;
182
- };
183
-
184
- // 记忆,见 3.5:这场会话的长期记忆,存在平台上
185
- memory: {
186
- list(): Promise<MemoryEntry[]>;
187
- write(key: string, text: string): Promise<void>;
188
- delete(key: string): Promise<void>;
189
- };
190
-
191
- // 授权数据,见 3.6:私聊不传 memberId,群聊传消息前缀里的 m_…
192
- dataAccess: {
193
- query(request: { datasets: string[]; memberId?: string }): Promise<DatasetAccessResult[]>;
194
- request(input: DataAccessRequestInput & { idempotencyKey: string }): Promise<void>;
195
- };
196
-
197
- // 小挂件,见 3.7
198
- widget: {
199
- list(): Promise<WidgetSummary[]>;
200
- create(input: { type: string; title: string; idempotencyKey?: string }): Promise<{ id: string }>;
201
- read(id: string): Promise<Widget>;
202
- readModule(id: string, module: string): Promise<{ records: WidgetRecord[] }>;
203
- rename(id: string, title: string): Promise<void>;
204
- writeRecord(
205
- id: string,
206
- input: { module: string; recordId: string; data: Record<string, unknown>; expectedRevision?: number },
207
- ): Promise<{ revision?: number }>;
208
- deleteRecord(id: string, module: string, recordId: string): Promise<void>;
209
- setPresentation(id: string, input: { summary: string; imageMediaId: string }): Promise<void>;
210
- send(id: string, options: { idempotencyKey: string; openTarget?: WidgetOpenTarget }): void;
211
- };
152
+ `buddy` 工厂拿到的当前会话对象。方法已绑定这场私聊或群聊,开发者可以在工具回调或其他会话业务代码中调用,不需要重复传会话键。
212
153
 
213
- // 主动服务,见 3.8:按稳定 key 订阅、查询和关闭;isRunning 判断现在是不是主动服务的回合
214
- proactive: {
215
- subscribe(key: string, request: ProactiveSubscriptionRequest): Promise<ProactiveSubscription>;
216
- list(): Promise<ProactiveSubscription[]>;
217
- unsubscribe(key: string): Promise<boolean>;
218
- unsubscribeAll(): Promise<number>;
219
- isRunning(): boolean;
220
- };
221
- }
222
- ```
154
+ | 成员 | 用途与详细说明 |
155
+ |---|---|
156
+ | `key` | 会话键,类型为 `string`;私聊以 `p_` 开头,群聊以 `g_` 开头,开发者自己的会话数据按它隔离 |
157
+ | `log` | `BuddyLogger`,日志带搭子编号 |
158
+ | `tools` | 平台提供的模型工具,按需放进 `initialState.tools`;见下方平台能力表 |
159
+ | `resource` | 读取工程里的只读资料,见 [3.1 资料](#31-资料) |
160
+ | `search` | 搜索网页或小红书,见 [3.2 搜索](#32-搜索) |
161
+ | `image` | 生成、发送、生成并发送图片,见 [3.4 图片消息](#34-图片消息) |
162
+ | `memory` | 读写当前会话的长期记忆,见 [3.5 记忆](#35-记忆) |
163
+ | `dataAccess` | 查询用户授权数据、请求授权,见 [3.6 用户授权数据](#36-用户授权数据) |
164
+ | `widget` | 创建、修改和发送小挂件,见 [3.7 小挂件](#37-小挂件) |
165
+ | `proactive` | 管理主动服务订阅,见 [3.8 主动服务](#38-主动服务) |
166
+ | `product` | 展示商品、读取和管理服务凭证,见 [3.9 商品与模拟购买](#39-商品与模拟购买) |
167
+
168
+ 各方法的参数、返回值和调用示例集中在对应能力一节。
223
169
 
224
170
  ## 3. 平台能力
225
171
 
@@ -228,7 +174,7 @@ interface Conversation {
228
174
  | 资料 | `conversation.resource.list` / `conversation.resource.read` | `resource_list` / `resource_read` |
229
175
  | 搜索 | `conversation.search.web` / `conversation.search.rednote` | `search_web` / `search_rednote` |
230
176
  | 提问 | 无 | `ask_question` |
231
- | 图片消息 | `conversation.image.send` | `send_image`(resources/ 里的文件或公网地址) |
177
+ | 图片消息 | `conversation.image.generate / send / generateAndSend` | `generate_image`、`send_image`、`generate_and_send_image`,与方法职责对应 |
232
178
  | 记忆 | `conversation.memory` | 无,开发者可以根据具体场景,自己组装成适合的模型工具 |
233
179
  | 用户授权数据 | `conversation.dataAccess.query` / `conversation.dataAccess.request` | `data_access_query` / `data_access_request` |
234
180
  | 搭子关系 | 无 | `buddy_relationship_request` |
@@ -246,7 +192,7 @@ buddy: (conversation) => ({
246
192
  }),
247
193
  ```
248
194
 
249
- `conversation` 上的方法在自己的模型工具里调,各节「`conversation` 对象方法」下都有一个完整的注册例子。
195
+ `conversation` 上的方法供开发者代码直接调用,也可以封装成业务专用的模型工具;`conversation.tools` 上的工具由平台提供,选入 `initialState.tools` 后主聊模型才能调用。
250
196
 
251
197
  ### 3.1 资料
252
198
 
@@ -439,69 +385,119 @@ await BuddyServer.start((conversation) => ({
439
385
 
440
386
  ### 3.4 图片消息
441
387
 
442
- 用户发送图片消息:模型自己看得到,不用开发者做任何事,模型不支持图片输入(如 deepseek 系列)时看不到图。
443
- 搭子发送图片消息:图从哪来不限:`resources/` 里的文件、搜索结果里的配图地址、搭子自己生成或其他方式,都通过接口上传图片,在消息里展现上传的图片(本质上和用户发送图片消息一样)。
388
+ 搭子可以看用户发来的图片、生成或修改图片,也可以发送已有图片。图片消息发进当前私聊或群聊;修改图片会发送一张新图,原图保留。
389
+
390
+ 新图片直接交给主聊模型;历史中只保留图片编号时,通过 `view_image` 取回内容。这个工具由 runtime 自动提供,不在 `conversation.tools` 上,无需开发者注册,自定义工具也不能使用这个名字。
444
391
 
445
392
  **`conversation` 对象方法**
446
393
 
447
394
  | 方法 | 做什么 |
448
395
  |---|---|
449
- | `image.send(input)` | 把一份图片字节传给平台,再写一条图片消息 |
396
+ | `image.generate(input, signal?)` | 只生成并保存图片,返回 `mediaId`,不发送聊天消息 |
397
+ | `image.send(input, signal?)` | 发送已有图片;传字节时上传并入队后返回,传 `mediaId` 时等待消息保存并返回 `mediaId` 和 `messageId` |
398
+ | `image.generateAndSend(input, signal?)` | 生成并发送,等待消息保存后返回 `mediaId` 和 `messageId` |
399
+
400
+ 方法供开发者代码直接调用,不依赖工具注册。生图使用 `initialState.imageModel`,应在工厂返回配置后调用;只发送已有图片不需要配置生图模型。`Conversation`、`ImageSendInput`、`ImageSendResult` 均可从 runtime 导入,类型中可选的方法在调用前先检查是否存在。
450
401
 
451
402
  ```ts
452
- image: {
453
- send(input: { bytes: Uint8Array; mimeType: "image/jpeg" | "image/png" | "image/webp"; text?: string; idempotencyKey: string }): Promise<void>;
403
+ type ImageSendInput = (
404
+ { bytes: Uint8Array; mimeType: "image/jpeg" | "image/png" | "image/webp"; mediaId?: never }
405
+ | { mediaId: string; bytes?: never; mimeType?: never } // 与图片字节二选一,须属于本搭子、本会话
406
+ ) & {
407
+ text?: string; // 可选配文,最多 500 字;默认无配文
408
+ idempotencyKey: string; // 非空;同一次发送沿用同一个键,只防止重复写消息,不能防止重复生图
409
+ };
410
+ interface ImageSendResult {
411
+ mediaId: string; // 已存图片的媒体编号,用于发送
412
+ messageId: string; // 已保存的图片消息编号,用于看图和参考图
413
+ }
414
+ interface Conversation {
415
+ image: {
416
+ generate?(input: {
417
+ prompt: string; // 必填,1 到 4000 字,本次完整画面要求
418
+ referenceMessageIds?: string[]; // 默认无参考图;当前会话的图片消息编号,按顺序填写,最多 14 张且不能重复
419
+ }, signal?: AbortSignal): Promise<{ mediaId: string }>;
420
+
421
+ send(input: Extract<ImageSendInput, { bytes: Uint8Array }>, signal?: AbortSignal): Promise<void>; // 字节上传并入队后返回
422
+ send(input: Extract<ImageSendInput, { mediaId: string }>, signal?: AbortSignal): Promise<ImageSendResult>; // 等消息保存后返回
423
+
424
+ generateAndSend?(input: {
425
+ prompt: string; // 与 generate 相同
426
+ referenceMessageIds?: string[]; // 与 generate 相同
427
+ text?: string; // 与 send 相同
428
+ idempotencyKey: string; // 与 send 相同
429
+ }, signal?: AbortSignal): Promise<ImageSendResult>;
430
+ };
454
431
  }
455
432
  ```
456
433
 
457
- 注册成模型工具:
434
+ 生图方法也可以参考当前会话的图片。例如,先生成并发送一个蓝色圆形,再把它改成橙色:第二次调用将前一张图的 `messageId` 放入 `referenceMessageIds`,`prompt` 写明「以图 1 为底图,只把圆形改成橙色,保留背景、构图和大小」。多张参考图按数组顺序对应图 1、图 2;卡片内图片使用「消息编号/第几张」。
435
+
436
+ 注册成模型工具:下面的业务工具只让模型填写活动标题,由开发者代码确定海报风格并调用 `image.generateAndSend`。
458
437
 
459
438
  ```ts
460
439
  await BuddyServer.start((conversation) => {
461
- const SEND_CHART_PARAMS = Type.Object({ week: Type.String({ description: "哪一周,如 2026-W41" }) });
462
- const sendWeightChart: AgentTool<typeof SEND_CHART_PARAMS, undefined> = {
463
- name: "send_weight_chart",
464
- label: "正在画体重曲线",
465
- description: "把这一周的体重曲线画成图发给用户。",
466
- parameters: SEND_CHART_PARAMS,
467
- execute: async (toolCallId, { week }) => {
468
- const png = await renderWeightChart(week); // 自己的画图代码,回 PNG 字节
469
- await conversation.image.send({ bytes: png, mimeType: "image/png", text: `${week} 的体重曲线`, idempotencyKey: `weight-chart:${toolCallId}` });
470
- return { content: [{ type: "text", text: "曲线图已发给用户。" }], details: undefined };
440
+ const PARAMS = Type.Object({ title: Type.String({ description: "活动标题" }) });
441
+ const makePoster: AgentTool<typeof PARAMS, undefined> = {
442
+ name: "make_event_poster",
443
+ label: "正在制作活动海报",
444
+ description: "根据活动标题生成并发送一张海报。成功后不要重复发送,失败时不要自行再次生成。",
445
+ parameters: PARAMS,
446
+ execute: async (callId, { title }, signal) => {
447
+ if (!conversation.image.generateAndSend) throw new Error("请使用支持生图的 runtime 版本");
448
+ const { messageId } = await conversation.image.generateAndSend({
449
+ prompt: `制作活动海报,标题为「${title}」,白底蓝字,简洁排版。`,
450
+ idempotencyKey: `event-poster:${callId}`,
451
+ }, signal);
452
+ return { content: [{ type: "text", text: `海报消息已保存。内部引用:[图片 #${messageId}],供看图与后续改图使用,不向用户展示编号。` }], details: undefined };
471
453
  },
472
454
  };
473
- return { initialState: { model: "kimi-k3", systemPrompt: PERSONA, tools: [sendWeightChart] } };
455
+ return { initialState: { model: "kimi-k3", imageModel: "Doubao-Seedream-5.0-lite", systemPrompt: "按用户要求制作海报。", tools: [makePoster] } };
474
456
  });
475
457
  ```
476
458
 
477
459
  **内建模型工具**
478
460
 
461
+ 不需要自定义业务逻辑时,直接从 `conversation.tools` 选择工具放进 `initialState.tools`,主聊模型就能调用。三个工具与上面的三个方法职责对应,均不会自动注册。
462
+
479
463
  | 工具 | 参数 | 结果 |
480
464
  |---|---|---|
481
- | `send_image` | `file` 与 `url` 二选一:`file` 是 `resources/` 里的图片路径(.jpg / .jpeg / .png / .webp);`url` 是图片的公网 http(s) 地址,比如 `search_rednote` 结果里的配图。`text` 可选,配一句话 | 图片进入发送队列 |
465
+ | `generate_image` | `prompt`、可选 `referenceMessageIds`,含义与 `image.generate` 相同 | 只生成,返回 `mediaId`,尚未发送 |
466
+ | `send_image` | `mediaId`、`file`、`url` 三选一,可附 `text`;`file` 是 `resources/` 内图片路径,`url` 是公网图片地址 | 传 `mediaId` 时等待消息保存并返回编号;文件或网址读取后上传,入队后返回 |
467
+ | `generate_and_send_image` | `prompt`、可选 `referenceMessageIds` 和 `text`,含义与 `image.generateAndSend` 相同 | 生成并发送,返回 `mediaId` 和 `messageId` |
482
468
 
483
- ```ts
484
- await BuddyServer.start((conversation) => ({
485
- initialState: {
486
- model: "kimi-k3",
487
- systemPrompt: "……搜到的笔记里有合适的配图,挑一两张用 send_image 发给用户,配一句说明。",
488
- tools: [conversation.tools.search_rednote, conversation.tools.send_image],
489
- },
490
- }));
491
- ```
469
+ 普通画图回复选入 `generate_and_send_image` 即可;需要生成、发送分开控制时,再选 `generate_image` 和 `send_image`。工具的消息幂等键由 runtime 填写。`systemPrompt` 说明什么时候生图、成功后不重复发送、失败时等待用户决定:
492
470
 
493
- **限制与异常**
471
+ ```ts
472
+ import { BuddyServer } from "@my-life-buddies/buddy-runtime";
494
473
 
495
- `image.send` 与 `send_image` 都受:
474
+ await BuddyServer.start((conversation) => {
475
+ const generateAndSend = conversation.tools.generate_and_send_image;
476
+ if (!generateAndSend) throw new Error("请使用支持生图的 runtime 版本");
477
+ return {
478
+ initialState: {
479
+ model: "kimi-k3",
480
+ imageModel: "Doubao-Seedream-5.0-lite",
481
+ systemPrompt: "用户要求画图或改图时用 generate_and_send_image。成功后不要重复发送或生成,不向用户展示内部编号。失败时告知用户,等待用户决定。",
482
+ tools: [generateAndSend],
483
+ },
484
+ };
485
+ });
486
+ ```
496
487
 
497
- - 原图不超过 5 MiB、宽高不超过 4096,否则抛异常;平台把图缩到最长边 2048 px、去掉 EXIF,格式不变(PNG 透明保留),处理后超过 5 MiB 也抛异常。
488
+ `model` 负责聊天并决定何时调用工具,`imageModel` 负责实际生图,二者分别配置。平台管理生图模型的接口和凭据,开发者只填写模型名称。
498
489
 
499
- `send_image` 另有,不满足即抛异常:
490
+ 使用内建生图工具改图时,由主聊模型将底图放在 `referenceMessageIds` 首位,再放必要的原图和历史版本,并在 `prompt` 中保留仍有效的画面要求。历史图片不会自动传给生图模型;`view_image` 只用于查看细节,不会自动加入参考图。内部编号不向用户展示。
500
491
 
501
- - `url`:http(s);不收 localhost 和私网 IP;不跟重定向;30 秒超时;响应 Content-Type 须为 JPEG、PNG 或 WebP。
502
- - `file`:须在 `resources/` 内,扩展名为 .jpg / .jpeg / .png / .webp。
492
+ **限制与异常**
503
493
 
504
- 开发者自己的工具不能叫 `view_image`:运行时内部用这个名字,重名时建会话抛异常。
494
+ - 生图不自动重试。超时、断线或取消可能已产生费用,结果未确认时等待用户决定。生成成功但发送失败时,`generateAndSend` 的报错保留 `mediaId` 和消息幂等键;确认需要重发后,只发送同一 `mediaId` 并沿用原键,不要重新生图。取消等待不撤回已入队的消息。
495
+ - 选入生图工具却未配置 `imageModel` 时,创建会话 Agent 报错;只使用方法时,在调用时检查配置。平台未配置或不支持模型时,明确提示本次没有调用生图服务。
496
+ - 每次生成一张 2048 × 2048 图片。
497
+ - 参考图使用图片消息的 `messageId`,不能填用于发送的 `mediaId`;只生成但未发送的图片不能直接作为参考图。参考图无权限、编号重复、超限或读取失败时停止生成,不忽略参考图继续生成。
498
+ - 发送图片字节、文件或地址时,每次重新上传。原图不超过 5 MiB、宽高不超过 4096;平台缩到最长边 2048 px、去掉 EXIF,保留格式和透明。处理后超过 5 MiB 时拒绝。发送 `mediaId` 不重复上传,平台检查会话归属。
499
+ - `send_image` 的 `file` 只收 `resources/` 内 .jpg/.jpeg/.png/.webp;`url` 只收 HTTP(S),拒绝 localhost 和字面量私网 IP,不跟重定向,30 秒超时,响应须为 JPEG/PNG/WebP。
500
+ - 查询过用户授权数据的回合不能生图,直接调用方法与模型工具都检查;主动服务回合采用相同规则。
505
501
 
506
502
  ### 3.5 记忆
507
503
 
@@ -580,7 +576,7 @@ await BuddyServer.start((conversation) => {
580
576
 
581
577
  ### 3.6 用户授权数据
582
578
 
583
- 用户同意后,搭子可以读用户的睡眠、运动、位置、日程等数据。数据按 Dataset 分项,用户逐项同意。搭子拿不到用户的真实 id。查到的数据只在当轮可见,见本节末尾。
579
+ 搭子可以按声明读取睡眠、运动、位置、日程和小红书数据。用户按 Dataset 授权给当前搭子;小红书登录只负责绑定账号,模型使用公开资料或画像前均需取得这项授权。搭子拿不到用户的真实 id。查到的数据只在当轮可见,见本节末尾。
584
580
 
585
581
  这里有三层不同概念:
586
582
 
@@ -643,7 +639,21 @@ const sleep = dataAccessCapability("health.sleep");
643
639
  | `device.location` | 当前位置 | `latitude`、`longitude`、`accuracy`、`timestamp` |
644
640
  | `device.calendar` | 未来日程 | `events` 数组,每条含 `title`、`startAt`、`endAt`、`isAllDay`、`calendar` |
645
641
 
646
- 部分字段可能缺失,取值前先判断。
642
+ 小红书公开资料与行为画像使用同一项声明:
643
+
644
+ | Dataset | `data` 内容 | 使用条件 |
645
+ |---|---|---|
646
+ | `xhs_user_behaviour_data` | `publicData` 为昵称、头像、简介及最多 20 条公开发布笔记 ID 样本;`memory` 为经过上游过滤的 RedKV 画像;收藏暂不透传 | 绑定小红书账号且用户授权给当前搭子;两个来源独立读取,部分缺失时只返回实际取得的部分 |
647
+
648
+ ```json
649
+ {
650
+ "requirements": [
651
+ { "id": "xhs_user_behaviour_data", "purpose": "根据用户的小红书行为偏好数据,做精准的规划推荐" }
652
+ ]
653
+ }
654
+ ```
655
+
656
+ 部分字段可能缺失,取值前先判断。画像来自已有的点点 Memory,搭搭不生成画像。两个上游都不可用时返回 `unavailable`,不要等待生成或重复申请授权。合并后的 Schema 为 v2;旧授权不会自动扩展范围,声明升级后用户需重新确认。旧 `xhs.profile` 和 `xhs.contentProfile` 声明继续识别,不自动转换为新授权。
647
657
 
648
658
  **`conversation` 对象方法**
649
659
 
@@ -667,7 +677,7 @@ interface DataAccessQuery {
667
677
 
668
678
  type DatasetAccessResult =
669
679
  | { id: string; status: "available"; schemaVersion: number; revision: number; sourceUpdatedAt: number; data: Record<string, unknown> } // data.items 的字段见上面各 Dataset 那张表
670
- | { id: string; status: "notDeclared" | "notGranted" | "disabledInGroup" | "unavailable" | "systemPermissionDenied" };
680
+ | { id: string; status: "relationshipRequired" | "notDeclared" | "notGranted" | "platformAuthorizationRequired" | "bindingRequired" | "disabledInGroup" | "unavailable" | "systemPermissionDenied" };
671
681
 
672
682
  interface DataAccessRequestInput {
673
683
  reason: string; // 为什么要这些数据,用户在授权卡上看到的就是这句
@@ -701,6 +711,18 @@ await BuddyServer.start((conversation) => {
701
711
 
702
712
  把 `conversation.tools.data_access_query`、`data_access_request` 放进 `initialState.tools`,模型就能自己查数据、发授权卡。放进去只决定模型能不能调用它们,不等于声明 Dataset,也不等于获得用户授权;没声明 Dataset 时照常注册,查询的每一项都回 `notDeclared`。
703
713
 
714
+ 业务明确某类问题必须先完成授权时,可在 `beforeReply(messages)` 中判断本轮消息、调用 `conversation.dataAccess.query` 检查状态;返回 `notGranted` 后调用 `conversation.dataAccess.request` 并返回 `true`。授权卡写回后本轮不会调用模型。用户处理授权卡后开启的新回合再次经过检查,再由模型按需读取目录和主题。
715
+
716
+ 查询 `xhs_user_behaviour_data` 时采用渐进式披露:首次不传 `profileRead`,返回 `data.view="index"`、可用的 `publicData`、`version` 和画像目录。结构化主题目录每页最多 8 项,字段为 `sectionId/topic/time/preference?/description`,不含具体详情;需要下一页时单独查询该 Dataset 并传入 `profileOffset: nextOffset`。旧文本目录使用 `sectionId/preview`,预览是片段开头最多 48 个字符,不是摘要。模型确有需要时,再单独查询该 Dataset 并传入 `profileRead: { version, sectionId }`,得到 `data.view="section"` 和 `text`,不会重复公开资料。只有公开资料时返回 `publicOnly`,只有画像时目录标记 `publicDataStatus="unavailable"`。不要遍历整份画像;`profileOffset` 与 `profileRead` 不能同时传。
717
+
718
+ 每段正文最多 4 KiB;同一回合所有成员的目录及片段 JSON 合计最多 24 KiB,重复读取也计入预算,耗尽返回 `budgetExceeded`。每次读取都重新查询平台权限;画像版本或成员改变时返回新目录,撤权时只返回权限状态。未知格式或超过 128 KiB 输入上限的画像返回 `unsupported`,不会退回全文。输入上限只约束 Runtime 接收的画像大小,模型仍按页读取目录和片段。
719
+
720
+ 这一限制作用于内置模型工具。`conversation.dataAccess.query` 仍返回原始 Dataset,供自定义业务代码处理;自定义工具也应按需选择字段,避免把完整 `memory` 放入提示词或工具结果。`memory` 可为主题对象或主题数组的 JSON 文本,主题字段按[画像逻辑细化 - 2026.10](https://docs.xiaohongshu.com/doc/eb9ad825bf1fabcd80913d7b4f6ac410)识别:`主题`、`关注时间`、可选数值 `偏好程度`、`描述`、字符串数组 `具体详情`。主题目录只含最多 50 字描述,正文只含详情,不透传未知字段;未知 JSON 封装返回 `unsupported`。文档没有定义 RedKV 的 value 外层封装,多主题数组为本地适配约定,实际接入时需确认。
721
+
722
+ 旧纯文本和 XML 按 UTF-8 大小分片;超长主题详情也按此规则续分。偏好分沿用上游值,不当作概率或重新计算;浏览、点赞、收藏和分享不代表用户亲身经历。
723
+
724
+ 平台提示词要求只读取当前问题相关的主题,分数高或领域相近不构成额外读取理由。回答区分画像记载、用户确认和待确认信息:未返回来源时不推测来自哪种行为,画像中的问句不当作已确定安排;用户限定只依据画像时,不补充未经验证的路线、价格等外部结论。这是模型使用规则,仍需通过实际调用与回答记录核验遵守情况。
725
+
704
726
  先查询,缺授权再发卡:
705
727
 
706
728
  ```ts
@@ -741,6 +763,8 @@ data_access_query
741
763
  | `available` | 读到了 |
742
764
  | `notDeclared` | 搭子没有声明这项数据 |
743
765
  | `notGranted` | 用户没有同意,或改过用途后还没重新同意 |
766
+ | `platformAuthorizationRequired` | 旧服务返回的状态,提示升级服务后重试,不发送授权卡 |
767
+ | `bindingRequired` | 小红书账号尚未完成可信绑定,可发卡引导绑定 |
744
768
  | `relationshipRequired` | 目标成员还没有添加当前搭子;群聊里可先发搭子名片 |
745
769
  | `disabledInGroup` | 这位成员在这个群里关掉了这项数据 |
746
770
  | `unavailable` | 平台上还没有这位用户的这项数据,或数据已过期 |
@@ -754,7 +778,7 @@ data_access_query
754
778
  `dataAccess.request` 与 `data_access_request`:
755
779
 
756
780
  - `relationshipRequired` 应先用 `buddy_relationship_request` 发搭子名片。
757
- - 发卡前须在同一回合查过对应 Dataset 且结果为 `notGranted`;`systemPermissionDenied` 引导用户去系统设置,其余状态不发卡。
781
+ - 发卡前须在同一回合查过对应 Dataset,状态为 `notGranted`,或小红书画像为 `bindingRequired`;`systemPermissionDenied` 引导用户去系统设置,其余状态不发卡。
758
782
  - 群聊里查询与发卡用同一个 `memberId`,取消息前缀里的 `#m_…`;卡片全群可见,只有目标成员能操作。
759
783
  - 发卡后本回合结束。
760
784
 
@@ -777,6 +801,8 @@ data_access_query
777
801
  - **不要把查到的数据往长期存的地方搬**。往 `memory` / `widget` / `proactive` 里写会被 Runtime 直接拒收;开发者工具未声明策略时按 `external` 拦截,声明为 `durable` / `external` 的工具既不能在已读数据后执行,也不能在自身执行过程中读取 DataAccess。直接绕过 Runtime 写文件或发网络请求不在工具 Gate 的控制范围内。
778
802
  - 只有查询结果真正包含 `available + data` 时才进入敏感状态;`notGranted`、`unavailable`、`disabledInGroup`、`notDeclared` 只是状态,不会误封后续操作。
779
803
 
804
+ 小红书行为画像复用现有 DataAccess 授权。用户在统一授权卡中确认当前搭子及用途后,搭搭为该用途按需读取已有画像;目录和详情每次查询都检查授权。
805
+
780
806
  #### 搭子关系工具
781
807
 
782
808
  `buddy_relationship_request` 用于群聊中目标成员明确索要名片,或尚未添加当前搭子而需要建立关系的情况。即使目标成员已经添加,也可以发送全群可见的名片;未添加的群成员可点击名片为自己添加搭子。只有 `memberId` 指定的目标成员原本未添加、随后完成添加时,平台才会用 `relationshipResult` 从持久历史开启新回合,继续原任务。已添加时名片仅供展示,不会触发新回合。发名片会结束当前回合,搭子不应轮询或在原回合等待用户操作;无需名片即可回答的问题应直接回答。私聊不可使用该工具。
@@ -1053,13 +1079,13 @@ beforeToolCall: async ({ toolCall }) =>
1053
1079
  ```ts
1054
1080
  await BuddyServer.start((conversation) => {
1055
1081
  if (!conversation.key.startsWith("p_")) {
1056
- return { initialState: { model: "deepseek-v4-flash", systemPrompt: "提供普通群聊帮助。", tools: [] } };
1082
+ return { initialState: { model: "deepseek-v4.1-flash-1017", systemPrompt: "提供普通群聊帮助。", tools: [] } };
1057
1083
  }
1058
1084
  const product = conversation.product;
1059
1085
  const showCard = conversation.tools.show_product_card;
1060
1086
  if (!product || !showCard) throw new Error("当前 Runtime 不支持商品能力");
1061
1087
  const basic = {
1062
- model: "deepseek-v4-flash" as const,
1088
+ model: "deepseek-v4.1-flash-1017" as const,
1063
1089
  systemPrompt: "提供基础养护帮助;用户有持续养护需求时可展示 plant_care 商品。",
1064
1090
  tools: [showCard],
1065
1091
  };
@@ -5,12 +5,17 @@ import type { MLBClient, WidgetType } from "../mlbClient.ts";
5
5
  import { DataAccessUsageTracker } from "../tools/dataAccess.ts";
6
6
  import type { Resource } from "../tools/resource.ts";
7
7
  import type { ProductEvent } from "../services/types.ts";
8
- import type { BuddyFactory, BuddyLogger, DataRequirement, MLBMessageToWrite } from "../types.ts";
8
+ import type { BuddyFactory, BuddyLogger, BuddyOptions, DataRequirement, MLBMessageToWrite } from "../types.ts";
9
9
  export interface AgentDeps {
10
10
  /** 当前普通用户回合的真实来源消息;模型参数不能覆盖。 */
11
11
  sourceMessage?: () => string | undefined;
12
12
  /** 这场会话的发送队列:小挂件引用、服务引用、权限请求卡、题卡都放进来 */
13
13
  enqueueMessage: (message: MLBMessageToWrite) => void;
14
+ /** 同一发送队列,等待这条消息的保存凭据。 */
15
+ enqueueMessageAndWait: (message: MLBMessageToWrite, signal?: AbortSignal) => Promise<{
16
+ messageId: string;
17
+ timestamp: number;
18
+ }>;
14
19
  /** 发了一张等用户操作的卡(权限请求卡、题卡):这张卡算这一轮说过的话 */
15
20
  onWaitingForUser: () => void;
16
21
  /** shouldStopAfterTurn 让这一轮停下了(发了等用户操作的卡,或开发者的钩子回 true):index.ts 据此不再接着 continue */
@@ -37,13 +42,16 @@ export interface AgentDeps {
37
42
  /** Agent 事件的去处。pi 会等它返回,所以只能同步做事 */
38
43
  onEvent: (agent: Agent, event: AgentEvent) => void;
39
44
  }
40
- /** createAgent 建好的:Agent、这场会话的模型条目、授权数据记账,以及开发者配的兜底话 */
45
+ /** createAgent 建好的:Agent、这场会话的模型条目、授权数据记账,以及开发者配的回复前检查和兜底话 */
41
46
  export interface CreatedAgent {
42
47
  prepareTurn: () => Promise<Model<Api>>;
43
48
  handleProductEvent: (event: ProductEvent) => Promise<string | undefined>;
44
49
  invalidateCredential: () => void;
45
50
  agent: Agent;
46
- /** 按开发者 initialState.model 造的模型条目:重建历史时抄身份按它 */
51
+ beforeReply: BuddyOptions["beforeReply"];
52
+ /** 回复前发卡不进入 Agent run,需要由会话层直接结束等待状态。 */
53
+ clearWaitingForUser: () => void;
54
+ /** 按开发者 initialState.model 造的模型条目:重建历史时抄身份、判断收不收图片都按它 */
47
55
  model: Model<Api>;
48
56
  dataAccessUsage: DataAccessUsageTracker;
49
57
  /** BuddyOptions.errorReply:普通回合出错或一句话没说时由 index.ts 的 runRound 发;没配是 undefined */
@@ -1 +1 @@
1
- {"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/conversation/agent.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/conversation/agent.ts"],"names":[],"mappings":"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"}
@@ -2,6 +2,7 @@
2
2
  // 管:造好这场会话的平台工具,拼好交给开发者工厂的 Conversation(会话键、日志、平台工具、进程启动时读好的资料、绑好会话键的记忆、授权数据查询、权限请求、主动服务与小挂件),跑工厂拿配置;
3
3
  // systemPrompt 按 平台的(GET /internal/v1/system-prompt,每场会话建 Agent 时拉一次)→ 开发者的 拼,段间空一行;
4
4
  // 工具清单 = 开发者的 initialState.tools(平台工具也是开发者从 conversation.tools 取来放进去的)+ 运行时自己挂的 view_image(只给收图片的模型,builtinTools),查重名;
5
+ // image 方法与图片工具共用实现;generate_image 只生成,generate_and_send_image 生成并等待消息保存;生图独立配置 initialState.imageModel。
5
6
  // 按 initialState.model 造网关的模型条目(id 运行时不支持就 throw,这场会话起不来);开发者显式设的 thinkingLevel 这个模型不支持时打一条 warn,写明支持哪几档、实际会怎么发;
6
7
  // 钩子叠加——开发者的 beforeToolCall / afterToolCall 包一层授权数据记账,beforeToolCall 抛错时打 warn 再抛给 pi(工具不执行,模型看到错误);
7
8
  // 发了等用户操作的卡(权限请求卡、题卡)的那一轮,shouldStopAfterTurn 在这一步收尾后停下;其余直通;
@@ -23,7 +24,9 @@ import { DATA_ACCESS_TAINTED_SUMMARY, DATA_ACCESS_TOOL_NAME, dataAccessSummary,
23
24
  import { resourceListTool, resourceReadTool } from "../tools/resource.js";
24
25
  import { assertUniqueToolNames } from "../tools/registry.js";
25
26
  import { widgetTools } from "../tools/widget.js";
26
- import { sendImage, sendImageTool } from "../tools/sendImage.js";
27
+ import { sendImageTool } from "../tools/sendImage.js";
28
+ import { generateImageTool, generateAndSendImageTool } from "../tools/generateImage.js";
29
+ import { createConversationImage } from "./image.js";
27
30
  import { viewImageTool } from "../tools/viewImage.js";
28
31
  import { dataAccessRequestTool, validateDataAccessRequest } from "../tools/dataAccessRequest.js";
29
32
  import { proactiveTools, validateProactiveSubscribe } from "../tools/proactive.js";
@@ -59,6 +62,8 @@ export async function createAgent(deps) {
59
62
  web: (input) => client.searchWeb(deps.key, input),
60
63
  rednote: (input) => client.searchRednote(deps.key, input),
61
64
  };
65
+ let imageModel;
66
+ const image = createConversationImage(client, deps.key, () => imageModel, deps.enqueueMessage, deps.enqueueMessageAndWait, () => dataAccessUsage.guardTool("image.generate", { retention: "external", result: "independent", arguments: "private" }));
62
67
  const platformTools = {
63
68
  show_product_card: productCardTool(product.api),
64
69
  proactive_subscribe: proactiveSubscribe,
@@ -79,7 +84,9 @@ export async function createAgent(deps) {
79
84
  widget_delete_record: widgetDeleteRecord,
80
85
  widget_delete: widgetDelete,
81
86
  widget_send: widgetSend,
82
- send_image: sendImageTool(client, deps.key, deps.resourcesDir, deps.enqueueMessage),
87
+ generate_image: generateImageTool(image),
88
+ generate_and_send_image: generateAndSendImageTool(image),
89
+ send_image: sendImageTool(image, deps.resourcesDir),
83
90
  search_web: searchWebTool(search),
84
91
  search_rednote: searchRednoteTool(search),
85
92
  };
@@ -90,8 +97,7 @@ export async function createAgent(deps) {
90
97
  log: deps.log,
91
98
  tools: platformTools,
92
99
  search,
93
- // 跟 send_image 走同一条路:传上平台拿 mediaId,再进这场会话的发送队列
94
- image: { send: (input) => sendImage(client, deps.key, deps.enqueueMessage, input) },
100
+ image,
95
101
  // 进程启动时读好的那份资料,开发者代码读它不用把 resource_list / resource_read 放进工具
96
102
  resource: {
97
103
  list: () => deps.resources.map((resource) => resource.file),
@@ -162,6 +168,10 @@ export async function createAgent(deps) {
162
168
  },
163
169
  };
164
170
  const options = await deps.factory(conversation);
171
+ imageModel = options.initialState.imageModel?.trim();
172
+ if (options.initialState.tools?.some(tool => tool === platformTools.generate_image || tool === platformTools.generate_and_send_image) && !imageModel) {
173
+ throw new Error("启用 generate_image 或 generate_and_send_image 时必须配置 initialState.imageModel");
174
+ }
165
175
  const model = createModel(deps.gatewayUrl, options.initialState.model);
166
176
  // 平台的 system prompt 拉失败就 throw:这场会话起不来,等下一条消息再重建
167
177
  const platformPrompt = await deps.client.systemPrompt(deps.key);
@@ -299,7 +309,8 @@ export async function createAgent(deps) {
299
309
  return nextModel;
300
310
  };
301
311
  return {
302
- agent, model, dataAccessUsage, errorReply: options.errorReply,
312
+ agent, model, dataAccessUsage, beforeReply: options.beforeReply,
313
+ clearWaitingForUser: () => { waitingForUser = false; }, errorReply: options.errorReply,
303
314
  invalidateCredential: () => product.invalidate(),
304
315
  prepareTurn: async () => {
305
316
  if (!supportsProducts || !options.configureTurn)