fix(agents): preserve string user content when merging turns · openclaw/openclaw@9061d1e
clawsweeper
·
2026-04-30
·
via Recent Commits to openclaw:main
| Original file line number | Diff line number | Diff line change |
|---|
@@ -357,6 +357,26 @@ describe("mergeConsecutiveUserTurns", () => {
|
357 | 357 | expect(merged.timestamp).toBe(2000); |
358 | 358 | }); |
359 | 359 | |
| 360 | +it("preserves string content while merging content", () => { |
| 361 | +const previous = { |
| 362 | +role: "user", |
| 363 | +content: "before", |
| 364 | +timestamp: 1000, |
| 365 | +} as Extract<AgentMessage, { role: "user" }>; |
| 366 | +const current = { |
| 367 | +role: "user", |
| 368 | +content: "after", |
| 369 | +timestamp: 2000, |
| 370 | +} as Extract<AgentMessage, { role: "user" }>; |
| 371 | + |
| 372 | +const merged = mergeConsecutiveUserTurns(previous, current); |
| 373 | + |
| 374 | +expect(merged.content).toEqual([ |
| 375 | +{ type: "text", text: "before" }, |
| 376 | +{ type: "text", text: "after" }, |
| 377 | +]); |
| 378 | +}); |
| 379 | + |
360 | 380 | it("backfills timestamp from earlier message when missing", () => { |
361 | 381 | const previous = { |
362 | 382 | role: "user", |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -10,6 +10,10 @@ type AnthropicContentBlock = {
|
10 | 10 | toolUseId?: string; |
11 | 11 | toolCallId?: string; |
12 | 12 | }; |
| 13 | +type UserContentBlock = Extract< |
| 14 | +Extract<AgentMessage, { role: "user" }>["content"], |
| 15 | +readonly unknown[] |
| 16 | +>[number]; |
13 | 17 | |
14 | 18 | function isToolCallBlock(block: AnthropicContentBlock): boolean { |
15 | 19 | return block.type === "toolUse" || block.type === "toolCall" || block.type === "functionCall"; |
@@ -350,8 +354,8 @@ export function mergeConsecutiveUserTurns(
|
350 | 354 | current: Extract<AgentMessage, { role: "user" }>, |
351 | 355 | ): Extract<AgentMessage, { role: "user" }> { |
352 | 356 | const mergedContent = [ |
353 | | - ...(Array.isArray(previous.content) ? previous.content : []), |
354 | | - ...(Array.isArray(current.content) ? current.content : []), |
| 357 | + ...normalizeUserContentForMerge(previous.content), |
| 358 | + ...normalizeUserContentForMerge(current.content), |
355 | 359 | ]; |
356 | 360 | |
357 | 361 | return { |
@@ -361,6 +365,16 @@ export function mergeConsecutiveUserTurns(
|
361 | 365 | }; |
362 | 366 | } |
363 | 367 | |
| 368 | +function normalizeUserContentForMerge(content: unknown): UserContentBlock[] { |
| 369 | +if (Array.isArray(content)) { |
| 370 | +return content as UserContentBlock[]; |
| 371 | +} |
| 372 | +if (typeof content === "string") { |
| 373 | +return [{ type: "text", text: content }]; |
| 374 | +} |
| 375 | +return []; |
| 376 | +} |
| 377 | + |
364 | 378 | /** |
365 | 379 | * Validates and fixes conversation turn sequences for Anthropic API. |
366 | 380 | * Anthropic requires strict alternating user→assistant pattern. |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -1141,17 +1141,9 @@ describe("sanitizeSessionHistory", () => {
|
1141 | 1141 | "```", |
1142 | 1142 | ].join("\n"); |
1143 | 1143 | const messages = castAgentMessages([ |
1144 | | -{ |
1145 | | -role: "user", |
1146 | | -content: [{ type: "text", text: "First" }], |
1147 | | -timestamp: nextTimestamp(), |
1148 | | -}, |
| 1144 | +makeUserMessage("First"), |
1149 | 1145 | makeAssistantMessage([{ type: "text", text: metadataOnlyText }]), |
1150 | | -{ |
1151 | | -role: "user", |
1152 | | -content: [{ type: "text", text: "Second" }], |
1153 | | -timestamp: nextTimestamp(), |
1154 | | -}, |
| 1146 | +makeUserMessage("Second"), |
1155 | 1147 | ]); |
1156 | 1148 | |
1157 | 1149 | const sanitized = await sanitizeSessionHistory({ |
|
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。