mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 09:30:43 +00:00
fix(agents): preserve string user content when merging turns
Co-authored-by: openclaw-clawsweeper[bot] <280122609+openclaw-clawsweeper[bot]@users.noreply.github.com> Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
This commit is contained in:
@@ -357,6 +357,26 @@ describe("mergeConsecutiveUserTurns", () => {
|
||||
expect(merged.timestamp).toBe(2000);
|
||||
});
|
||||
|
||||
it("preserves string content while merging content", () => {
|
||||
const previous = {
|
||||
role: "user",
|
||||
content: "before",
|
||||
timestamp: 1000,
|
||||
} as Extract<AgentMessage, { role: "user" }>;
|
||||
const current = {
|
||||
role: "user",
|
||||
content: "after",
|
||||
timestamp: 2000,
|
||||
} as Extract<AgentMessage, { role: "user" }>;
|
||||
|
||||
const merged = mergeConsecutiveUserTurns(previous, current);
|
||||
|
||||
expect(merged.content).toEqual([
|
||||
{ type: "text", text: "before" },
|
||||
{ type: "text", text: "after" },
|
||||
]);
|
||||
});
|
||||
|
||||
it("backfills timestamp from earlier message when missing", () => {
|
||||
const previous = {
|
||||
role: "user",
|
||||
|
||||
@@ -10,6 +10,10 @@ type AnthropicContentBlock = {
|
||||
toolUseId?: string;
|
||||
toolCallId?: string;
|
||||
};
|
||||
type UserContentBlock = Extract<
|
||||
Extract<AgentMessage, { role: "user" }>["content"],
|
||||
readonly unknown[]
|
||||
>[number];
|
||||
|
||||
function isToolCallBlock(block: AnthropicContentBlock): boolean {
|
||||
return block.type === "toolUse" || block.type === "toolCall" || block.type === "functionCall";
|
||||
@@ -350,8 +354,8 @@ export function mergeConsecutiveUserTurns(
|
||||
current: Extract<AgentMessage, { role: "user" }>,
|
||||
): Extract<AgentMessage, { role: "user" }> {
|
||||
const mergedContent = [
|
||||
...(Array.isArray(previous.content) ? previous.content : []),
|
||||
...(Array.isArray(current.content) ? current.content : []),
|
||||
...normalizeUserContentForMerge(previous.content),
|
||||
...normalizeUserContentForMerge(current.content),
|
||||
];
|
||||
|
||||
return {
|
||||
@@ -361,6 +365,16 @@ export function mergeConsecutiveUserTurns(
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeUserContentForMerge(content: unknown): UserContentBlock[] {
|
||||
if (Array.isArray(content)) {
|
||||
return content as UserContentBlock[];
|
||||
}
|
||||
if (typeof content === "string") {
|
||||
return [{ type: "text", text: content }];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates and fixes conversation turn sequences for Anthropic API.
|
||||
* Anthropic requires strict alternating user→assistant pattern.
|
||||
|
||||
@@ -1141,17 +1141,9 @@ describe("sanitizeSessionHistory", () => {
|
||||
"```",
|
||||
].join("\n");
|
||||
const messages = castAgentMessages([
|
||||
{
|
||||
role: "user",
|
||||
content: [{ type: "text", text: "First" }],
|
||||
timestamp: nextTimestamp(),
|
||||
},
|
||||
makeUserMessage("First"),
|
||||
makeAssistantMessage([{ type: "text", text: metadataOnlyText }]),
|
||||
{
|
||||
role: "user",
|
||||
content: [{ type: "text", text: "Second" }],
|
||||
timestamp: nextTimestamp(),
|
||||
},
|
||||
makeUserMessage("Second"),
|
||||
]);
|
||||
|
||||
const sanitized = await sanitizeSessionHistory({
|
||||
|
||||
Reference in New Issue
Block a user