@my-life-buddies/buddy-runtime 0.0.2 → 0.1.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 (49) hide show
  1. package/README.md +121 -21
  2. package/dist/conversation/agent.d.ts +8 -4
  3. package/dist/conversation/agent.d.ts.map +1 -1
  4. package/dist/conversation/agent.js +38 -31
  5. package/dist/conversation/agent.js.map +1 -1
  6. package/dist/conversation/agentMessageLLMMessageConverter.d.ts +8 -0
  7. package/dist/conversation/agentMessageLLMMessageConverter.d.ts.map +1 -0
  8. package/dist/conversation/agentMessageLLMMessageConverter.js +48 -0
  9. package/dist/conversation/agentMessageLLMMessageConverter.js.map +1 -0
  10. package/dist/conversation/convert.d.ts +11 -11
  11. package/dist/conversation/convert.d.ts.map +1 -1
  12. package/dist/conversation/convert.js +51 -29
  13. package/dist/conversation/convert.js.map +1 -1
  14. package/dist/conversation/history.d.ts +14 -14
  15. package/dist/conversation/history.d.ts.map +1 -1
  16. package/dist/conversation/history.js +19 -21
  17. package/dist/conversation/history.js.map +1 -1
  18. package/dist/conversation/index.d.ts +26 -25
  19. package/dist/conversation/index.d.ts.map +1 -1
  20. package/dist/conversation/index.js +106 -104
  21. package/dist/conversation/index.js.map +1 -1
  22. package/dist/conversation/mlbMessageAgentMessageConverter.d.ts +36 -0
  23. package/dist/conversation/mlbMessageAgentMessageConverter.d.ts.map +1 -0
  24. package/dist/conversation/mlbMessageAgentMessageConverter.js +224 -0
  25. package/dist/conversation/mlbMessageAgentMessageConverter.js.map +1 -0
  26. package/dist/conversation/readMLBMessages.d.ts +29 -0
  27. package/dist/conversation/readMLBMessages.d.ts.map +1 -0
  28. package/dist/conversation/readMLBMessages.js +86 -0
  29. package/dist/conversation/readMLBMessages.js.map +1 -0
  30. package/dist/conversation/writeMLBMessage.d.ts +62 -0
  31. package/dist/conversation/writeMLBMessage.d.ts.map +1 -0
  32. package/dist/conversation/writeMLBMessage.js +212 -0
  33. package/dist/conversation/writeMLBMessage.js.map +1 -0
  34. package/dist/index.d.ts +1 -1
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/mlbClient.d.ts +17 -6
  37. package/dist/mlbClient.d.ts.map +1 -1
  38. package/dist/mlbClient.js +58 -4
  39. package/dist/mlbClient.js.map +1 -1
  40. package/dist/server.d.ts.map +1 -1
  41. package/dist/server.js +14 -1
  42. package/dist/server.js.map +1 -1
  43. package/dist/tools/dataAccess.d.ts +48 -0
  44. package/dist/tools/dataAccess.d.ts.map +1 -0
  45. package/dist/tools/dataAccess.js +131 -0
  46. package/dist/tools/dataAccess.js.map +1 -0
  47. package/dist/types.d.ts +48 -14
  48. package/dist/types.d.ts.map +1 -1
  49. package/package.json +1 -1
@@ -0,0 +1,224 @@
1
+ // MLBMessage 与 AgentMessage 之间的换算,正反两个方向。只管形状换算,不碰网络、不碰 Agent、不管游标和幂等键。
2
+ // MLBMessage → AgentMessage:GET /internal/v1/messages 拉回来的记录换成 Agent 历史(重建历史、交给回合都用它)。
3
+ // AgentMessage → MLBMessage:pi 跑出来的 assistant / toolResult / note 换成 POST /internal/v1/messages 的每条 body。
4
+ // 每次请求模型前 AgentMessage → LLMMessage 那一步在 agentMessageLLMMessageConverter.ts。
5
+ import { DATA_ACCESS_TOOL_NAME, SENSITIVE_RUN_TOOL_SUMMARY, dataAccessSummary, isDataAccessDetails } from "../tools/dataAccess.js";
6
+ /** 工具结果没回来(回合被中断)时,写给模型和平台的占位文本 */
7
+ const MISSING_TOOL_RESULT = "(工具结果未返回:回合被中断)";
8
+ /** system 记录换成 user 消息时加在正文前的说明 */
9
+ const SYSTEM_EVENT_PREFIX = "(群里发生了一件事)";
10
+ /** 转发卡最多展开几层,再深的一层写成一句省略 */
11
+ const CARD_MAX_DEPTH = 3;
12
+ const CARD_TOO_DEEP = "(更深的转发略)";
13
+ /** 重建出来的 thinking 块用的签名:pi 的 openai-completions 回放时按它把推理原文写回 reasoning_content 字段 */
14
+ const THINKING_SIGNATURE = "reasoning_content";
15
+ /** 重建的 assistant 消息没有真实用量,全零;形状照 pi-agent-core 的 EMPTY_USAGE */
16
+ function emptyUsage() {
17
+ return {
18
+ input: 0,
19
+ output: 0,
20
+ cacheRead: 0,
21
+ cacheWrite: 0,
22
+ totalTokens: 0,
23
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
24
+ };
25
+ }
26
+ // —— MLBMessage → AgentMessage
27
+ /**
28
+ * 把一批平台记录换成 pi 的历史。跳过的:interrupted 的半截话、question / widget、空正文又没工具调用的搭子记录。
29
+ * 对方的 message 变 user(群里带成员前缀)、system 变 user(带「群里发生了一件事」)、messagesCard 拍平成 user、
30
+ * 平台的场合交代 context 原样变 user(开头自带「平台交代给你的情况」那句用法,不套前缀);
31
+ * 搭子的 message / checkin 变 assistant,tool 变 assistant + 每个调用一条 toolResult,note 变 note。
32
+ */
33
+ export function mlbMessagesToAgentMessages(records, identity) {
34
+ const out = [];
35
+ for (const record of records) {
36
+ if (record.interrupted)
37
+ continue;
38
+ const text = record.text ?? "";
39
+ switch (record.type) {
40
+ case "system":
41
+ out.push(buildUserMessage(SYSTEM_EVENT_PREFIX + text, record.timestamp));
42
+ break;
43
+ case "context":
44
+ if (text)
45
+ out.push(buildUserMessage(text, record.timestamp));
46
+ break;
47
+ case "messagesCard":
48
+ if (record.messagesCard)
49
+ out.push(buildUserMessage(flattenCard(record.messagesCard, 1).join("\n"), record.timestamp));
50
+ break;
51
+ case "message":
52
+ case "checkin":
53
+ if (record.author === "buddy") {
54
+ if (text)
55
+ out.push(buildAssistantMessage(record, [...thinkingBlocks(record.thinking), { type: "text", text }], "stop", identity));
56
+ }
57
+ else if (record.type === "message" && text) {
58
+ out.push(buildUserMessage(memberPrefix(record) + text, record.timestamp));
59
+ }
60
+ break;
61
+ case "tool":
62
+ if (record.author === "buddy")
63
+ out.push(...toolMLBMessageToAgentMessages(record, text, identity));
64
+ break;
65
+ case "note":
66
+ if (record.author === "buddy" && text)
67
+ out.push({ role: "note", text, timestamp: record.timestamp });
68
+ break;
69
+ default:
70
+ // question / widget 和别的类型:模型不需要
71
+ break;
72
+ }
73
+ }
74
+ return out;
75
+ }
76
+ /** 搭子的 tool 记录:一条带 toolCall 块的 assistant,后面每个调用跟一条 toolResult */
77
+ function toolMLBMessageToAgentMessages(record, text, identity) {
78
+ const calls = record.toolCalls ?? [];
79
+ if (!text && calls.length === 0)
80
+ return [];
81
+ const content = [...thinkingBlocks(record.thinking)];
82
+ if (text)
83
+ content.push({ type: "text", text });
84
+ for (const call of calls)
85
+ content.push({ type: "toolCall", id: call.callId, name: call.name, arguments: argumentsOf(call) });
86
+ const assistant = buildAssistantMessage(record, content, calls.length > 0 ? "toolUse" : "stop", identity);
87
+ const results = calls.map((call) => ({
88
+ role: "toolResult",
89
+ toolCallId: call.callId,
90
+ toolName: call.name,
91
+ content: [{ type: "text", text: call.result ?? MISSING_TOOL_RESULT }],
92
+ isError: call.result === undefined,
93
+ timestamp: record.timestamp,
94
+ }));
95
+ return [assistant, ...results];
96
+ }
97
+ function buildAssistantMessage(record, content, stopReason, identity) {
98
+ return {
99
+ role: "assistant",
100
+ content,
101
+ api: identity.api,
102
+ provider: identity.provider,
103
+ model: identity.model,
104
+ usage: emptyUsage(),
105
+ stopReason,
106
+ timestamp: record.timestamp,
107
+ };
108
+ }
109
+ function buildUserMessage(text, timestamp) {
110
+ return { role: "user", content: [{ type: "text", text }], timestamp };
111
+ }
112
+ /** 有推理原文才放 thinking 块,没有就一个不放 */
113
+ function thinkingBlocks(thinking) {
114
+ return thinking ? [{ type: "thinking", thinking, thinkingSignature: THINKING_SIGNATURE }] : [];
115
+ }
116
+ /** pi 的 toolCall 块要 arguments 是对象;平台存的不是对象就当空参数 */
117
+ function argumentsOf(call) {
118
+ const value = call.arguments;
119
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
120
+ }
121
+ /** 群成员说的话前面标谁说的:有成员号写 `[名字 #m_…] `,没成员号只写 `[名字] `;私聊没名字不标 */
122
+ function memberPrefix(record) {
123
+ const name = record.name;
124
+ const memberId = record.memberId;
125
+ if (name && memberId)
126
+ return `[${name} #${memberId}] `;
127
+ if (name)
128
+ return `[${name}] `;
129
+ if (memberId)
130
+ return `[#${memberId}] `;
131
+ return "";
132
+ }
133
+ /** 转发卡拍平成行:每条先一行 `【名字】`,正文原样跟一行;嵌套卡接着展开,超过层数上限写一句省略 */
134
+ function flattenCard(card, depth) {
135
+ const lines = [];
136
+ for (const item of card.items ?? []) {
137
+ lines.push(`【${item.name ?? ""}】`);
138
+ if (item.text)
139
+ lines.push(item.text);
140
+ if (item.messagesCard) {
141
+ if (depth >= CARD_MAX_DEPTH)
142
+ lines.push(CARD_TOO_DEEP);
143
+ else
144
+ lines.push(...flattenCard(item.messagesCard, depth + 1));
145
+ }
146
+ }
147
+ return lines;
148
+ }
149
+ // —— AgentMessage → MLBMessage(POST /internal/v1/messages 的每条 body)
150
+ /**
151
+ * 不带工具调用的 assistant 消息写成 message 记录。
152
+ * 正常收尾的照写(带 thinking);error / aborted 收尾但已经说了半截的写成 interrupted;其余不写。
153
+ */
154
+ export function assistantMessageToMLBMessage(m, omitThinking = false) {
155
+ if (m.content.some((block) => block.type === "toolCall"))
156
+ return null;
157
+ const text = textOf(m);
158
+ if (m.stopReason === "error" || m.stopReason === "aborted") {
159
+ return text ? { type: "message", text, interrupted: true } : null;
160
+ }
161
+ const thinking = omitThinking ? undefined : thinkingOf(m);
162
+ return thinking === undefined ? { type: "message", text } : { type: "message", text, thinking };
163
+ }
164
+ /**
165
+ * 带工具调用的 assistant 消息连同它的结果写成一条 tool 记录;每个调用的 summary 取工具的 label,没有 label 用名字。
166
+ * 没有工具调用的返回 null(那种走 assistantMessageToMLBMessage)。
167
+ */
168
+ export function toolStepToMLBMessage(m, toolResults, tools, dataAccessDetailsOf, sensitiveRun = false) {
169
+ const calls = m.content.filter((block) => block.type === "toolCall");
170
+ if (calls.length === 0)
171
+ return null;
172
+ const toolCalls = calls.map((call) => {
173
+ const result = toolResults.find((r) => r.toolCallId === call.id);
174
+ const label = tools.find((t) => t.name === call.name)?.label;
175
+ return {
176
+ callId: call.id,
177
+ name: call.name,
178
+ summary: label || call.name,
179
+ arguments: sensitiveRun ? undefined : call.arguments,
180
+ result: result === undefined
181
+ ? MISSING_TOOL_RESULT
182
+ : toolResultTextToWrite(call.name, result, dataAccessDetailsOf?.(call.id), sensitiveRun),
183
+ };
184
+ });
185
+ const thinking = sensitiveRun ? undefined : thinkingOf(m);
186
+ const record = { type: "tool", text: sensitiveRun ? "" : textOf(m), toolCalls };
187
+ if (thinking !== undefined)
188
+ record.thinking = thinking;
189
+ return record;
190
+ }
191
+ /** DataAccess 的真实 Tool 结果只给当前模型回合看;写平台时只留 Dataset ID 与状态。 */
192
+ function toolResultTextToWrite(toolName, result, trackedDetails, sensitiveRun) {
193
+ const details = trackedDetails ?? result.details;
194
+ if (toolName === DATA_ACCESS_TOOL_NAME || isDataAccessDetails(details))
195
+ return dataAccessSummary(details);
196
+ return sensitiveRun ? SENSITIVE_RUN_TOOL_SUMMARY : resultText(result);
197
+ }
198
+ /** note 消息写成 note 记录 */
199
+ export function noteMessageToMLBMessage(n) {
200
+ return { type: "note", text: n.text };
201
+ }
202
+ /** assistant 消息里所有 thinking 块的推理原文拼起来;一个字都没有返回 undefined */
203
+ export function thinkingOf(m) {
204
+ const thinking = m.content
205
+ .filter((block) => block.type === "thinking")
206
+ .map((block) => block.thinking)
207
+ .join("");
208
+ return thinking === "" ? undefined : thinking;
209
+ }
210
+ /** assistant 消息里所有 text 块的正文拼起来 */
211
+ export function textOf(m) {
212
+ return m.content
213
+ .filter((block) => block.type === "text")
214
+ .map((block) => block.text)
215
+ .join("");
216
+ }
217
+ /** 工具结果的文本:多个 text 块用换行接起来(跟 pi 的 openai-completions 回放时一样) */
218
+ function resultText(result) {
219
+ return result.content
220
+ .filter((block) => block.type === "text")
221
+ .map((block) => block.text)
222
+ .join("\n");
223
+ }
224
+ //# sourceMappingURL=mlbMessageAgentMessageConverter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mlbMessageAgentMessageConverter.js","sourceRoot":"","sources":["../../src/conversation/mlbMessageAgentMessageConverter.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,uFAAuF;AACvF,0GAA0G;AAC1G,6EAA6E;AAK7E,OAAO,EAAE,qBAAqB,EAAE,0BAA0B,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AASnI,mCAAmC;AACnC,MAAM,mBAAmB,GAAG,iBAAiB,CAAC;AAE9C,mCAAmC;AACnC,MAAM,mBAAmB,GAAG,YAAY,CAAC;AAEzC,4BAA4B;AAC5B,MAAM,cAAc,GAAG,CAAC,CAAC;AACzB,MAAM,aAAa,GAAG,UAAU,CAAC;AAEjC,qFAAqF;AACrF,MAAM,kBAAkB,GAAG,mBAAmB,CAAC;AAE/C,gEAAgE;AAChE,SAAS,UAAU;IAClB,OAAO;QACN,KAAK,EAAE,CAAC;QACR,MAAM,EAAE,CAAC;QACT,SAAS,EAAE,CAAC;QACZ,UAAU,EAAE,CAAC;QACb,WAAW,EAAE,CAAC;QACd,IAAI,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE;KACpE,CAAC;AACH,CAAC;AAED,+BAA+B;AAE/B;;;;;GAKG;AACH,MAAM,UAAU,0BAA0B,CAAC,OAAqB,EAAE,QAAuB;IACxF,MAAM,GAAG,GAAmB,EAAE,CAAC;IAC/B,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC9B,IAAI,MAAM,CAAC,WAAW;YAAE,SAAS;QACjC,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QAC/B,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;YACrB,KAAK,QAAQ;gBACZ,GAAG,CAAC,IAAI,CAAC,gBAAgB,CAAC,mBAAmB,GAAG,IAAI,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC;gBACzE,MAAM;YACP,KAAK,SAAS;gBACb,IAAI,IAAI;oBAAE,GAAG,CAAC,IAAI,CAAC,gBAAgB,CAAC,IAAI,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC;gBAC7D,MAAM;YACP,KAAK,cAAc;gBAClB,IAAI,MAAM,CAAC,YAAY;oBAAE,GAAG,CAAC,IAAI,CAAC,gBAAgB,CAAC,WAAW,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC;gBACtH,MAAM;YACP,KAAK,SAAS,CAAC;YACf,KAAK,SAAS;gBACb,IAAI,MAAM,CAAC,MAAM,KAAK,OAAO,EAAE,CAAC;oBAC/B,IAAI,IAAI;wBAAE,GAAG,CAAC,IAAI,CAAC,qBAAqB,CAAC,MAAM,EAAE,CAAC,GAAG,cAAc,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;gBACnI,CAAC;qBAAM,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,IAAI,IAAI,EAAE,CAAC;oBAC9C,GAAG,CAAC,IAAI,CAAC,gBAAgB,CAAC,YAAY,CAAC,MAAM,CAAC,GAAG,IAAI,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC;gBAC3E,CAAC;gBACD,MAAM;YACP,KAAK,MAAM;gBACV,IAAI,MAAM,CAAC,MAAM,KAAK,OAAO;oBAAE,GAAG,CAAC,IAAI,CAAC,GAAG,6BAA6B,CAAC,MAAM,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC;gBAClG,MAAM;YACP,KAAK,MAAM;gBACV,IAAI,MAAM,CAAC,MAAM,KAAK,OAAO,IAAI,IAAI;oBAAE,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC;gBACrG,MAAM;YACP;gBACC,gCAAgC;gBAChC,MAAM;QACR,CAAC;IACF,CAAC;IACD,OAAO,GAAG,CAAC;AACZ,CAAC;AAED,iEAAiE;AACjE,SAAS,6BAA6B,CAAC,MAAkB,EAAE,IAAY,EAAE,QAAuB;IAC/F,MAAM,KAAK,GAAG,MAAM,CAAC,SAAS,IAAI,EAAE,CAAC;IACrC,IAAI,CAAC,IAAI,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAC3C,MAAM,OAAO,GAAgC,CAAC,GAAG,cAAc,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;IAClF,IAAI,IAAI;QAAE,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;IAC/C,KAAK,MAAM,IAAI,IAAI,KAAK;QAAE,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC7H,MAAM,SAAS,GAAG,qBAAqB,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAC1G,MAAM,OAAO,GAAwB,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACzD,IAAI,EAAE,YAAY;QAClB,UAAU,EAAE,IAAI,CAAC,MAAM;QACvB,QAAQ,EAAE,IAAI,CAAC,IAAI;QACnB,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,IAAI,mBAAmB,EAAE,CAAC;QACrE,OAAO,EAAE,IAAI,CAAC,MAAM,KAAK,SAAS;QAClC,SAAS,EAAE,MAAM,CAAC,SAAS;KAC3B,CAAC,CAAC,CAAC;IACJ,OAAO,CAAC,SAAS,EAAE,GAAG,OAAO,CAAC,CAAC;AAChC,CAAC;AAED,SAAS,qBAAqB,CAC7B,MAAkB,EAClB,OAAoC,EACpC,UAA0C,EAC1C,QAAuB;IAEvB,OAAO;QACN,IAAI,EAAE,WAAW;QACjB,OAAO;QACP,GAAG,EAAE,QAAQ,CAAC,GAAG;QACjB,QAAQ,EAAE,QAAQ,CAAC,QAAQ;QAC3B,KAAK,EAAE,QAAQ,CAAC,KAAK;QACrB,KAAK,EAAE,UAAU,EAAE;QACnB,UAAU;QACV,SAAS,EAAE,MAAM,CAAC,SAAS;KAC3B,CAAC;AACH,CAAC;AAED,SAAS,gBAAgB,CAAC,IAAY,EAAE,SAAiB;IACxD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC;AACvE,CAAC;AAED,iCAAiC;AACjC,SAAS,cAAc,CAAC,QAA4B;IACnD,OAAO,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AAChG,CAAC;AAED,mDAAmD;AACnD,SAAS,WAAW,CAAC,IAAiB;IACrC,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC;IAC7B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAE,KAAiC,CAAC,CAAC,CAAC,EAAE,CAAC;AACvH,CAAC;AAED,6DAA6D;AAC7D,SAAS,YAAY,CAAC,MAAkB;IACvC,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;IACzB,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;IACjC,IAAI,IAAI,IAAI,QAAQ;QAAE,OAAO,IAAI,IAAI,KAAK,QAAQ,IAAI,CAAC;IACvD,IAAI,IAAI;QAAE,OAAO,IAAI,IAAI,IAAI,CAAC;IAC9B,IAAI,QAAQ;QAAE,OAAO,KAAK,QAAQ,IAAI,CAAC;IACvC,OAAO,EAAE,CAAC;AACX,CAAC;AAED,uDAAuD;AACvD,SAAS,WAAW,CAAC,IAAkB,EAAE,KAAa;IACrD,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,IAAI,EAAE,EAAE,CAAC;QACrC,KAAK,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,IAAI,EAAE,GAAG,CAAC,CAAC;QACnC,IAAI,IAAI,CAAC,IAAI;YAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACrC,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YACvB,IAAI,KAAK,IAAI,cAAc;gBAAE,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;;gBAClD,KAAK,CAAC,IAAI,CAAC,GAAG,WAAW,CAAC,IAAI,CAAC,YAAY,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC;QAC/D,CAAC;IACF,CAAC;IACD,OAAO,KAAK,CAAC;AACd,CAAC;AAED,oEAAoE;AAEpE;;;GAGG;AACH,MAAM,UAAU,4BAA4B,CAAC,CAAmB,EAAE,YAAY,GAAG,KAAK;IACrF,IAAI,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,UAAU,CAAC;QAAE,OAAO,IAAI,CAAC;IACtE,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IACvB,IAAI,CAAC,CAAC,UAAU,KAAK,OAAO,IAAI,CAAC,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;QAC5D,OAAO,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IACnE,CAAC;IACD,MAAM,QAAQ,GAAG,YAAY,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IAC1D,OAAO,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;AACjG,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CACnC,CAAmB,EACnB,WAAgC,EAChC,KAAwC,EACxC,mBAAqD,EACrD,YAAY,GAAG,KAAK;IAEpB,MAAM,KAAK,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,EAAqB,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC;IACxF,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACpC,MAAM,SAAS,GAAkB,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QACnD,MAAM,MAAM,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,KAAK,IAAI,CAAC,EAAE,CAAC,CAAC;QACjE,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC;QAC7D,OAAO;YACN,MAAM,EAAE,IAAI,CAAC,EAAE;YACf,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,OAAO,EAAE,KAAK,IAAI,IAAI,CAAC,IAAI;YAC3B,SAAS,EAAE,YAAY,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS;YACpD,MAAM,EACL,MAAM,KAAK,SAAS;gBACnB,CAAC,CAAC,mBAAmB;gBACrB,CAAC,CAAC,qBAAqB,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,mBAAmB,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,YAAY,CAAC;SAC1F,CAAC;IACH,CAAC,CAAC,CAAC;IACH,MAAM,QAAQ,GAAG,YAAY,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IAC1D,MAAM,MAAM,GAAsB,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC;IACnG,IAAI,QAAQ,KAAK,SAAS;QAAE,MAAM,CAAC,QAAQ,GAAG,QAAQ,CAAC;IACvD,OAAO,MAAM,CAAC;AACf,CAAC;AAED,6DAA6D;AAC7D,SAAS,qBAAqB,CAAC,QAAgB,EAAE,MAAyB,EAAE,cAAuB,EAAE,YAAqB;IACzH,MAAM,OAAO,GAAG,cAAc,IAAI,MAAM,CAAC,OAAO,CAAC;IACjD,IAAI,QAAQ,KAAK,qBAAqB,IAAI,mBAAmB,CAAC,OAAO,CAAC;QAAE,OAAO,iBAAiB,CAAC,OAAO,CAAC,CAAC;IAC1G,OAAO,YAAY,CAAC,CAAC,CAAC,0BAA0B,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;AACvE,CAAC;AAED,wBAAwB;AACxB,MAAM,UAAU,uBAAuB,CAAC,CAAc;IACrD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;AACvC,CAAC;AAED,4DAA4D;AAC5D,MAAM,UAAU,UAAU,CAAC,CAAmB;IAC7C,MAAM,QAAQ,GAAG,CAAC,CAAC,OAAO;SACxB,MAAM,CAAC,CAAC,KAAK,EAA4B,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,UAAU,CAAC;SACtE,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC;SAC9B,IAAI,CAAC,EAAE,CAAC,CAAC;IACX,OAAO,QAAQ,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC;AAC/C,CAAC;AAED,mCAAmC;AACnC,MAAM,UAAU,MAAM,CAAC,CAAmB;IACzC,OAAO,CAAC,CAAC,OAAO;SACd,MAAM,CAAC,CAAC,KAAK,EAAwB,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,MAAM,CAAC;SAC9D,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC;SAC1B,IAAI,CAAC,EAAE,CAAC,CAAC;AACZ,CAAC;AAED,+DAA+D;AAC/D,SAAS,UAAU,CAAC,MAAyB;IAC5C,OAAO,MAAM,CAAC,OAAO;SACnB,MAAM,CAAC,CAAC,KAAK,EAAwB,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,MAAM,CAAC;SAC9D,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC;SAC1B,IAAI,CAAC,IAAI,CAAC,CAAC;AACd,CAAC"}
@@ -0,0 +1,29 @@
1
+ import type { MLBClient } from "../mlbClient.ts";
2
+ import type { BuddyLogger, MLBMessage } from "../types.ts";
3
+ export interface ReadMLBMessagesDeps {
4
+ client: MLBClient;
5
+ key: string;
6
+ log: BuddyLogger;
7
+ /** 一次拉取拉到的消息,按消息时间升序:第一次是这场会话的全部,之后只有比上一批晚的,可能是空批 */
8
+ onMessages: (messages: MLBMessage[]) => Promise<void>;
9
+ }
10
+ export declare class ReadMLBMessages {
11
+ private readonly deps;
12
+ /** 拉取串行链:notify 只往链尾排,绝不并发拉 */
13
+ private chain;
14
+ private retry;
15
+ private retryDelay;
16
+ /** 读方向游标:已经拉到的最晚一条的消息时间。别人的消息按到达平台的时刻记、只会越来越晚,拿它往后拉不会漏 */
17
+ private pulledTimestamp;
18
+ private closed;
19
+ constructor(deps: ReadMLBMessagesDeps);
20
+ /** 有新消息了:把一次拉取排进串行链,取消正在等的重试。退役后忽略 */
21
+ notify(): void;
22
+ /** 退役:取消重试,之后不再拉,已经在拉的拉完也不交出去 */
23
+ close(): void;
24
+ /** 一次拉取:从游标往后翻到不满页,整批里比游标晚的交给 onMessages。网络错误排一次重试就返回,这批不交 */
25
+ private read;
26
+ private scheduleRetry;
27
+ private cancelRetry;
28
+ }
29
+ //# sourceMappingURL=readMLBMessages.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"readMLBMessages.d.ts","sourceRoot":"","sources":["../../src/conversation/readMLBMessages.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAO3D,MAAM,WAAW,mBAAmB;IACnC,MAAM,EAAE,SAAS,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,WAAW,CAAC;IACjB,qDAAqD;IACrD,UAAU,EAAE,CAAC,QAAQ,EAAE,UAAU,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACtD;AAED,qBAAa,eAAe;IAC3B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAsB;IAC3C,+BAA+B;IAC/B,OAAO,CAAC,KAAK,CAAoC;IACjD,OAAO,CAAC,KAAK,CAA4C;IACzD,OAAO,CAAC,UAAU,CAAqB;IACvC,0DAA0D;IAC1D,OAAO,CAAC,eAAe,CAAK;IAC5B,OAAO,CAAC,MAAM,CAAS;gBAEX,IAAI,EAAE,mBAAmB;IAIrC,sCAAsC;IACtC,MAAM,IAAI,IAAI;IAQd,iCAAiC;IACjC,KAAK,IAAI,IAAI;IAKb,8DAA8D;YAChD,IAAI;IAyBlB,OAAO,CAAC,aAAa;IAWrB,OAAO,CAAC,WAAW;CAKnB"}
@@ -0,0 +1,86 @@
1
+ // 一场会话从平台拉消息:同会话串行地拉、从哪拉起、拉失败了怎么办。
2
+ // 管:拉取串行链(notify 只往链尾排,绝不并发拉);读方向游标(已经拉到的最晚一条的消息时间);
3
+ // GET /internal/v1/messages 从游标往后翻页到不满页,整批里比游标晚的交给 onMessages,游标推到这批里最晚的消息时间;
4
+ // 网络错误主动退避重试(1 秒起步、翻倍、封顶 30 秒),新的 notify 取消等待立即重拉;退役后不再拉。
5
+ // 不管:拉到的消息怎么处理(index.ts:第一批重建历史,之后的交给回合)、HTTP 请求本身(mlbClient.ts)。
6
+ import { errorText } from "../log.js";
7
+ /** 每页拉多少条:平台钳在 1..200,翻页靠不满页 */
8
+ const PULL_LIMIT = 200;
9
+ const PULL_RETRY_MIN_MS = 1_000;
10
+ const PULL_RETRY_MAX_MS = 30_000;
11
+ export class ReadMLBMessages {
12
+ deps;
13
+ /** 拉取串行链:notify 只往链尾排,绝不并发拉 */
14
+ chain = Promise.resolve();
15
+ retry;
16
+ retryDelay = PULL_RETRY_MIN_MS;
17
+ /** 读方向游标:已经拉到的最晚一条的消息时间。别人的消息按到达平台的时刻记、只会越来越晚,拿它往后拉不会漏 */
18
+ pulledTimestamp = 0;
19
+ closed = false;
20
+ constructor(deps) {
21
+ this.deps = deps;
22
+ }
23
+ /** 有新消息了:把一次拉取排进串行链,取消正在等的重试。退役后忽略 */
24
+ notify() {
25
+ if (this.closed)
26
+ return;
27
+ this.cancelRetry();
28
+ this.chain = this.chain
29
+ .then(() => this.read())
30
+ .catch((e) => this.deps.log.warn("处理拉取结果出错", { convoKey: this.deps.key, error: errorText(e) }));
31
+ }
32
+ /** 退役:取消重试,之后不再拉,已经在拉的拉完也不交出去 */
33
+ close() {
34
+ this.closed = true;
35
+ this.cancelRetry();
36
+ }
37
+ /** 一次拉取:从游标往后翻到不满页,整批里比游标晚的交给 onMessages。网络错误排一次重试就返回,这批不交 */
38
+ async read() {
39
+ if (this.closed)
40
+ return;
41
+ const batch = [];
42
+ let after = this.pulledTimestamp;
43
+ for (;;) {
44
+ let page;
45
+ try {
46
+ page = await this.deps.client.pullMessages(this.deps.key, after, PULL_LIMIT);
47
+ }
48
+ catch (e) {
49
+ this.deps.log.warn("拉消息失败,稍后主动重试", { convoKey: this.deps.key, after, delayMs: this.retryDelay, error: errorText(e) });
50
+ this.scheduleRetry();
51
+ return;
52
+ }
53
+ this.cancelRetry();
54
+ if (this.closed)
55
+ return;
56
+ batch.push(...page.messages);
57
+ if (page.messages.length < PULL_LIMIT)
58
+ break;
59
+ after = page.messages[page.messages.length - 1].timestamp;
60
+ }
61
+ this.retryDelay = PULL_RETRY_MIN_MS;
62
+ const fresh = batch.filter((m) => m.timestamp > this.pulledTimestamp);
63
+ for (const m of fresh)
64
+ if (m.timestamp > this.pulledTimestamp)
65
+ this.pulledTimestamp = m.timestamp;
66
+ await this.deps.onMessages(fresh);
67
+ }
68
+ scheduleRetry() {
69
+ if (this.closed || this.retry !== undefined)
70
+ return;
71
+ const delay = this.retryDelay;
72
+ this.retryDelay = Math.min(delay * 2, PULL_RETRY_MAX_MS);
73
+ this.retry = setTimeout(() => {
74
+ this.retry = undefined;
75
+ this.notify();
76
+ }, delay);
77
+ this.retry.unref();
78
+ }
79
+ cancelRetry() {
80
+ if (this.retry === undefined)
81
+ return;
82
+ clearTimeout(this.retry);
83
+ this.retry = undefined;
84
+ }
85
+ }
86
+ //# sourceMappingURL=readMLBMessages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"readMLBMessages.js","sourceRoot":"","sources":["../../src/conversation/readMLBMessages.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ import type { AgentMessage } from "@earendil-works/pi-agent-core";
2
+ import type { AssistantMessage, ToolResultMessage } from "@earendil-works/pi-ai";
3
+ import type { MLBClient } from "../mlbClient.ts";
4
+ import type { BuddyLogger, NoteMessage } from "../types.ts";
5
+ export interface WriteMLBMessageDeps {
6
+ client: MLBClient;
7
+ key: string;
8
+ buddyId: string;
9
+ /** 本进程里这场会话的一次生命周期标识,进幂等键;重启后换一个,旧键不会撞新键 */
10
+ runId: string;
11
+ log: BuddyLogger;
12
+ }
13
+ export declare class WriteMLBMessage {
14
+ private readonly deps;
15
+ private queue;
16
+ /** 串行链正在跑:它在跑时队头那项正在发 */
17
+ private sending;
18
+ /** 幂等键里的自增号 */
19
+ private sent;
20
+ private retryDelay;
21
+ /** close 时打断重发等待 */
22
+ private readonly abort;
23
+ /** 等队列清空的人 */
24
+ private waiters;
25
+ private deltaBuffer;
26
+ private deltaTimer;
27
+ private closed;
28
+ constructor(deps: WriteMLBMessageDeps);
29
+ /** 回合开始 on、收尾 off;工具跑起来时带 status 更新状态行。退役后照收 */
30
+ replying(data: {
31
+ on: boolean;
32
+ status?: string;
33
+ }): void;
34
+ /** 正文碎片先攒着,120 毫秒内攒到的一起发 */
35
+ delta(text: string): void;
36
+ /** 不带工具调用的 assistant 消息写成 message 记录(mlbMessageAgentMessageConverter.ts 判定不写的不写) */
37
+ assistant(m: AssistantMessage, omitThinking?: boolean): void;
38
+ /** 带工具调用的一步连同工具结果写成一条 tool 记录;平台回的时间写在这一步的 assistant 消息上,工具结果跟着它排 */
39
+ toolStep(m: AssistantMessage, toolResults: ToolResultMessage[], tools: {
40
+ name: string;
41
+ label: string;
42
+ }[], dataAccessDetailsOf: (toolCallId: string) => unknown, sensitiveRun?: boolean): void;
43
+ /** 运行时补给模型的 note 写成 note 记录 */
44
+ note(n: NoteMessage): void;
45
+ /** DataAccess 脱敏把这一轮的消息换成了副本(old → new):还没写完的,平台回的时间改写到副本上 */
46
+ retarget(replaced: Map<AgentMessage, AgentMessage>): void;
47
+ /** 攒着的字发出去,等队列清空(退役后只剩开关要发) */
48
+ flush(): Promise<void>;
49
+ /** 退役:攒着的字扔掉,正在退避等重发的立刻醒来;队列里没发的字与写回在串行链上丢掉,开关照发 */
50
+ close(): void;
51
+ /** 入队即分配幂等键;先把攒着的字发掉,字排在写回前面。退役后不收 */
52
+ private write;
53
+ /** 攒下的碎片立刻排进队列;队尾正好是一帧还没发的 delta 就并进去(写回在重发时,后面的字攒成一帧) */
54
+ private flushDelta;
55
+ private enqueue;
56
+ /** 串行链:一次发队头一项,发完(或退役丢掉)才出队;写回遇到网络错误留在队头,退避后带同一个键重发 */
57
+ private drain;
58
+ /** 写一条:delivered → 平台回的时间写进内存那条消息;rejected(4xx)→ warn;都算发完回 true。throw(网络 / 5xx)→ 退避后回 false,留队重发 */
59
+ private send;
60
+ private clearTimer;
61
+ }
62
+ //# sourceMappingURL=writeMLBMessage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"writeMLBMessage.d.ts","sourceRoot":"","sources":["../../src/conversation/writeMLBMessage.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAGjF,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,iBAAiB,CAAC;AAC9D,OAAO,KAAK,EAAE,WAAW,EAAqB,WAAW,EAAE,MAAM,aAAa,CAAC;AAS/E,MAAM,WAAW,mBAAmB;IACnC,MAAM,EAAE,SAAS,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,4CAA4C;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,WAAW,CAAC;CACjB;AAQD,qBAAa,eAAe;IAC3B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAsB;IAC3C,OAAO,CAAC,KAAK,CAAc;IAC3B,yBAAyB;IACzB,OAAO,CAAC,OAAO,CAAS;IACxB,eAAe;IACf,OAAO,CAAC,IAAI,CAAK;IACjB,OAAO,CAAC,UAAU,CAAsB;IACxC,oBAAoB;IACpB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAyB;IAC/C,cAAc;IACd,OAAO,CAAC,OAAO,CAAsB;IACrC,OAAO,CAAC,WAAW,CAAM;IACzB,OAAO,CAAC,UAAU,CAA4C;IAC9D,OAAO,CAAC,MAAM,CAAS;gBAEX,IAAI,EAAE,mBAAmB;IAIrC,gDAAgD;IAChD,QAAQ,CAAC,IAAI,EAAE;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAKtD,4BAA4B;IAC5B,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAWzB,oFAAoF;IACpF,SAAS,CAAC,CAAC,EAAE,gBAAgB,EAAE,YAAY,CAAC,EAAE,OAAO,GAAG,IAAI;IAK5D,qEAAqE;IACrE,QAAQ,CACP,CAAC,EAAE,gBAAgB,EACnB,WAAW,EAAE,iBAAiB,EAAE,EAChC,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,EACxC,mBAAmB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,EACpD,YAAY,CAAC,EAAE,OAAO,GACpB,IAAI;IAKP,+BAA+B;IAC/B,IAAI,CAAC,CAAC,EAAE,WAAW,GAAG,IAAI;IAI1B,8DAA8D;IAC9D,QAAQ,CAAC,QAAQ,EAAE,GAAG,CAAC,YAAY,EAAE,YAAY,CAAC,GAAG,IAAI;IASzD,+BAA+B;IAC/B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAQtB,oDAAoD;IACpD,KAAK,IAAI,IAAI;IAOb,sCAAsC;IACtC,OAAO,CAAC,KAAK;IAQb,0DAA0D;IAC1D,OAAO,CAAC,UAAU;IAWlB,OAAO,CAAC,OAAO;IAKf,uDAAuD;YACzC,KAAK;IAyBnB,qGAAqG;YACvF,IAAI;IAiClB,OAAO,CAAC,UAAU;CAKlB"}
@@ -0,0 +1,212 @@
1
+ // 一场会话往平台发的所有东西排在一条队列里按顺序发:「正在回复」开关与工具状态行、生成中的正文碎片(delta)、写回的消息。
2
+ // 为什么一条队列(REDoc《【技术设计】搭子消息的时间与顺序》):平台把搭子消息的时间记成它第一个字到达平台的时刻——
3
+ // 上一次写回之后收到的第一段 delta,由下一条模型消息的写回领走。字和写回排在一条队列里,平台收到的先后就是模型出字的先后;
4
+ // 一条写回在重发,后面的字跟着等,领不错。
5
+ // 管:delta 每 120 毫秒攒一帧,写回与开关之前先把攒着的发掉;入参是 pi 消息,mlbMessageAgentMessageConverter.ts 换成写口 body;入队那一刻分配幂等键(之后重发一字不变);
6
+ // POST /internal/v1/messages 成功后把平台回的时间写进内存里那条消息的 timestamp(回合收尾按它排);4xx 丢弃并 warn(平台明确拒绝,重发没用);
7
+ // 网络错误 / 5xx 留队退避重发(1 秒起步、翻倍、封顶 10 秒);等队列清空;退役时丢掉没发的字与写回、打断重发等待,开关照发(收尾要把三个圆点熄掉)。
8
+ // 不管:什么时候开关、什么时候写(index.ts 按 Agent 事件调这里)、HTTP 请求本身(mlbClient.ts)。
9
+ import { assistantMessageToMLBMessage, noteMessageToMLBMessage, toolStepToMLBMessage } from "./mlbMessageAgentMessageConverter.js";
10
+ import { errorText } from "../log.js";
11
+ /** 生成中的正文碎片攒多久发一帧 delta */
12
+ const DELTA_THROTTLE_MS = 120;
13
+ /** 写平台遇到网络错误 / 5xx 的重发间隔 */
14
+ const WRITE_RETRY_MIN_MS = 1_000;
15
+ const WRITE_RETRY_MAX_MS = 10_000;
16
+ export class WriteMLBMessage {
17
+ deps;
18
+ queue = [];
19
+ /** 串行链正在跑:它在跑时队头那项正在发 */
20
+ sending = false;
21
+ /** 幂等键里的自增号 */
22
+ sent = 0;
23
+ retryDelay = WRITE_RETRY_MIN_MS;
24
+ /** close 时打断重发等待 */
25
+ abort = new AbortController();
26
+ /** 等队列清空的人 */
27
+ waiters = [];
28
+ deltaBuffer = "";
29
+ deltaTimer;
30
+ closed = false;
31
+ constructor(deps) {
32
+ this.deps = deps;
33
+ }
34
+ /** 回合开始 on、收尾 off;工具跑起来时带 status 更新状态行。退役后照收 */
35
+ replying(data) {
36
+ this.flushDelta();
37
+ this.enqueue({ kind: "replying", data });
38
+ }
39
+ /** 正文碎片先攒着,120 毫秒内攒到的一起发 */
40
+ delta(text) {
41
+ if (text === "" || this.closed)
42
+ return;
43
+ this.deltaBuffer += text;
44
+ if (this.deltaTimer === undefined) {
45
+ this.deltaTimer = setTimeout(() => {
46
+ this.deltaTimer = undefined;
47
+ this.flushDelta();
48
+ }, DELTA_THROTTLE_MS);
49
+ }
50
+ }
51
+ /** 不带工具调用的 assistant 消息写成 message 记录(mlbMessageAgentMessageConverter.ts 判定不写的不写) */
52
+ assistant(m, omitThinking) {
53
+ const out = assistantMessageToMLBMessage(m, omitThinking);
54
+ if (out !== null)
55
+ this.write(out, m);
56
+ }
57
+ /** 带工具调用的一步连同工具结果写成一条 tool 记录;平台回的时间写在这一步的 assistant 消息上,工具结果跟着它排 */
58
+ toolStep(m, toolResults, tools, dataAccessDetailsOf, sensitiveRun) {
59
+ const out = toolStepToMLBMessage(m, toolResults, tools, dataAccessDetailsOf, sensitiveRun);
60
+ if (out !== null)
61
+ this.write(out, m);
62
+ }
63
+ /** 运行时补给模型的 note 写成 note 记录 */
64
+ note(n) {
65
+ this.write(noteMessageToMLBMessage(n), n);
66
+ }
67
+ /** DataAccess 脱敏把这一轮的消息换成了副本(old → new):还没写完的,平台回的时间改写到副本上 */
68
+ retarget(replaced) {
69
+ if (replaced.size === 0)
70
+ return;
71
+ for (const item of this.queue) {
72
+ if (item.kind !== "write")
73
+ continue;
74
+ const next = replaced.get(item.target);
75
+ if (next !== undefined)
76
+ item.target = next;
77
+ }
78
+ }
79
+ /** 攒着的字发出去,等队列清空(退役后只剩开关要发) */
80
+ flush() {
81
+ this.flushDelta();
82
+ if (!this.sending && this.queue.length === 0)
83
+ return Promise.resolve();
84
+ return new Promise((resolve) => {
85
+ this.waiters.push(resolve);
86
+ });
87
+ }
88
+ /** 退役:攒着的字扔掉,正在退避等重发的立刻醒来;队列里没发的字与写回在串行链上丢掉,开关照发 */
89
+ close() {
90
+ this.closed = true;
91
+ this.abort.abort();
92
+ this.clearTimer();
93
+ this.deltaBuffer = "";
94
+ }
95
+ /** 入队即分配幂等键;先把攒着的字发掉,字排在写回前面。退役后不收 */
96
+ write(message, target) {
97
+ if (this.closed)
98
+ return;
99
+ this.flushDelta();
100
+ this.sent += 1;
101
+ const idempotencyKey = `buddy:${this.deps.buddyId}:${this.deps.key}:${this.deps.runId}:${this.sent}`;
102
+ this.enqueue({ kind: "write", message: { ...message, idempotencyKey }, target });
103
+ }
104
+ /** 攒下的碎片立刻排进队列;队尾正好是一帧还没发的 delta 就并进去(写回在重发时,后面的字攒成一帧) */
105
+ flushDelta() {
106
+ this.clearTimer();
107
+ const text = this.deltaBuffer;
108
+ this.deltaBuffer = "";
109
+ if (text === "" || this.closed)
110
+ return;
111
+ const tail = this.queue[this.queue.length - 1];
112
+ const tailInFlight = this.sending && this.queue.length === 1;
113
+ if (tail !== undefined && tail.kind === "delta" && !tailInFlight)
114
+ tail.text += text;
115
+ else
116
+ this.enqueue({ kind: "delta", text });
117
+ }
118
+ enqueue(item) {
119
+ this.queue.push(item);
120
+ void this.drain();
121
+ }
122
+ /** 串行链:一次发队头一项,发完(或退役丢掉)才出队;写回遇到网络错误留在队头,退避后带同一个键重发 */
123
+ async drain() {
124
+ if (this.sending)
125
+ return;
126
+ this.sending = true;
127
+ try {
128
+ while (this.queue.length > 0) {
129
+ const head = this.queue[0];
130
+ if (head.kind === "replying") {
131
+ await this.deps.client.push(this.deps.key, "replying", head.data);
132
+ }
133
+ else if (this.closed) {
134
+ // 退役了:没发的字与写回丢掉
135
+ }
136
+ else if (head.kind === "delta") {
137
+ await this.deps.client.push(this.deps.key, "delta", { text: head.text });
138
+ }
139
+ else if (!(await this.send(head))) {
140
+ continue;
141
+ }
142
+ this.queue.shift();
143
+ }
144
+ }
145
+ finally {
146
+ this.sending = false;
147
+ const waiters = this.waiters;
148
+ this.waiters = [];
149
+ for (const resolve of waiters)
150
+ resolve();
151
+ }
152
+ }
153
+ /** 写一条:delivered → 平台回的时间写进内存那条消息;rejected(4xx)→ warn;都算发完回 true。throw(网络 / 5xx)→ 退避后回 false,留队重发 */
154
+ async send(item) {
155
+ const head = item.message;
156
+ let result;
157
+ try {
158
+ result = await this.deps.client.writeMessages(this.deps.key, [head]);
159
+ }
160
+ catch (e) {
161
+ this.deps.log.warn("写消息失败,稍后重发", {
162
+ convoKey: this.deps.key,
163
+ idempotencyKey: head.idempotencyKey,
164
+ delayMs: this.retryDelay,
165
+ error: errorText(e),
166
+ });
167
+ await waitMs(this.retryDelay, this.abort.signal);
168
+ this.retryDelay = Math.min(this.retryDelay * 2, WRITE_RETRY_MAX_MS);
169
+ return false;
170
+ }
171
+ this.retryDelay = WRITE_RETRY_MIN_MS;
172
+ if (result.kind === "delivered") {
173
+ const timestamp = result.timestamps[0];
174
+ if (typeof timestamp === "number")
175
+ item.target.timestamp = timestamp;
176
+ this.deps.log.info("写回一条", { convoKey: this.deps.key, type: head.type, chars: head.text?.length ?? 0, idempotencyKey: head.idempotencyKey, timestamp });
177
+ }
178
+ else {
179
+ this.deps.log.warn("写消息被平台拒绝,丢弃", {
180
+ convoKey: this.deps.key,
181
+ idempotencyKey: head.idempotencyKey,
182
+ type: head.type,
183
+ status: result.status,
184
+ body: result.body,
185
+ });
186
+ }
187
+ return true;
188
+ }
189
+ clearTimer() {
190
+ if (this.deltaTimer === undefined)
191
+ return;
192
+ clearTimeout(this.deltaTimer);
193
+ this.deltaTimer = undefined;
194
+ }
195
+ }
196
+ /** 等 ms 毫秒;signal 触发就提前返回(不 reject) */
197
+ function waitMs(ms, signal) {
198
+ return new Promise((resolve) => {
199
+ if (signal.aborted) {
200
+ resolve();
201
+ return;
202
+ }
203
+ const finish = () => {
204
+ clearTimeout(timer);
205
+ signal.removeEventListener("abort", finish);
206
+ resolve();
207
+ };
208
+ const timer = setTimeout(finish, ms);
209
+ signal.addEventListener("abort", finish, { once: true });
210
+ });
211
+ }
212
+ //# sourceMappingURL=writeMLBMessage.js.map