mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 10:01:24 +00:00
refactor(shared): reuse chat content extractor for assistant text
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import type { AgentMessage } from "@mariozechner/pi-agent-core";
|
import type { AgentMessage } from "@mariozechner/pi-agent-core";
|
||||||
import type { AssistantMessage } from "@mariozechner/pi-ai";
|
import type { AssistantMessage } from "@mariozechner/pi-ai";
|
||||||
|
import { extractTextFromChatContent } from "../shared/chat-content.js";
|
||||||
import { stripReasoningTagsFromText } from "../shared/text/reasoning-tags.js";
|
import { stripReasoningTagsFromText } from "../shared/text/reasoning-tags.js";
|
||||||
import { sanitizeUserFacingText } from "./pi-embedded-helpers.js";
|
import { sanitizeUserFacingText } from "./pi-embedded-helpers.js";
|
||||||
import { formatToolDetail, resolveToolDisplay } from "./tool-display.js";
|
import { formatToolDetail, resolveToolDisplay } from "./tool-display.js";
|
||||||
@@ -207,25 +208,15 @@ export function stripThinkingTagsFromText(text: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function extractAssistantText(msg: AssistantMessage): string {
|
export function extractAssistantText(msg: AssistantMessage): string {
|
||||||
const isTextBlock = (block: unknown): block is { type: "text"; text: string } => {
|
const extracted =
|
||||||
if (!block || typeof block !== "object") {
|
extractTextFromChatContent(msg.content, {
|
||||||
return false;
|
sanitizeText: (text) =>
|
||||||
}
|
stripThinkingTagsFromText(
|
||||||
const rec = block as Record<string, unknown>;
|
stripDowngradedToolCallText(stripMinimaxToolCallXml(text)),
|
||||||
return rec.type === "text" && typeof rec.text === "string";
|
).trim(),
|
||||||
};
|
joinWith: "\n",
|
||||||
|
normalizeText: (text) => text.trim(),
|
||||||
const blocks = Array.isArray(msg.content)
|
}) ?? "";
|
||||||
? msg.content
|
|
||||||
.filter(isTextBlock)
|
|
||||||
.map((c) =>
|
|
||||||
stripThinkingTagsFromText(
|
|
||||||
stripDowngradedToolCallText(stripMinimaxToolCallXml(c.text)),
|
|
||||||
).trim(),
|
|
||||||
)
|
|
||||||
.filter(Boolean)
|
|
||||||
: [];
|
|
||||||
const extracted = blocks.join("\n").trim();
|
|
||||||
// Only apply keyword-based error rewrites when the assistant message is actually an error.
|
// Only apply keyword-based error rewrites when the assistant message is actually an error.
|
||||||
// Otherwise normal prose that *mentions* errors (e.g. "context overflow") can get clobbered.
|
// Otherwise normal prose that *mentions* errors (e.g. "context overflow") can get clobbered.
|
||||||
const errorContext = msg.stopReason === "error" || Boolean(msg.errorMessage?.trim());
|
const errorContext = msg.stopReason === "error" || Boolean(msg.errorMessage?.trim());
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export {
|
|||||||
resolveSessionReference,
|
resolveSessionReference,
|
||||||
shouldResolveSessionIdInput,
|
shouldResolveSessionIdInput,
|
||||||
} from "./sessions-resolution.js";
|
} from "./sessions-resolution.js";
|
||||||
|
import { extractTextFromChatContent } from "../../shared/chat-content.js";
|
||||||
import { sanitizeUserFacingText } from "../pi-embedded-helpers.js";
|
import { sanitizeUserFacingText } from "../pi-embedded-helpers.js";
|
||||||
import {
|
import {
|
||||||
stripDowngradedToolCallText,
|
stripDowngradedToolCallText,
|
||||||
@@ -152,23 +153,12 @@ export function extractAssistantText(message: unknown): string | undefined {
|
|||||||
if (!Array.isArray(content)) {
|
if (!Array.isArray(content)) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
const chunks: string[] = [];
|
const joined =
|
||||||
for (const block of content) {
|
extractTextFromChatContent(content, {
|
||||||
if (!block || typeof block !== "object") {
|
sanitizeText: sanitizeTextContent,
|
||||||
continue;
|
joinWith: "",
|
||||||
}
|
normalizeText: (text) => text.trim(),
|
||||||
if ((block as { type?: unknown }).type !== "text") {
|
}) ?? "";
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const text = (block as { text?: unknown }).text;
|
|
||||||
if (typeof text === "string") {
|
|
||||||
const sanitized = sanitizeTextContent(text);
|
|
||||||
if (sanitized.trim()) {
|
|
||||||
chunks.push(sanitized);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const joined = chunks.join("").trim();
|
|
||||||
const stopReason = (message as { stopReason?: unknown }).stopReason;
|
const stopReason = (message as { stopReason?: unknown }).stopReason;
|
||||||
const errorMessage = (message as { errorMessage?: unknown }).errorMessage;
|
const errorMessage = (message as { errorMessage?: unknown }).errorMessage;
|
||||||
const errorContext =
|
const errorContext =
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
export function extractTextFromChatContent(
|
export function extractTextFromChatContent(
|
||||||
content: unknown,
|
content: unknown,
|
||||||
opts?: { sanitizeText?: (text: string) => string },
|
opts?: {
|
||||||
|
sanitizeText?: (text: string) => string;
|
||||||
|
joinWith?: string;
|
||||||
|
normalizeText?: (text: string) => string;
|
||||||
|
},
|
||||||
): string | null {
|
): string | null {
|
||||||
const normalize = (text: string) => text.replace(/\s+/g, " ").trim();
|
const normalize = opts?.normalizeText ?? ((text: string) => text.replace(/\s+/g, " ").trim());
|
||||||
|
const joinWith = opts?.joinWith ?? " ";
|
||||||
|
|
||||||
if (typeof content === "string") {
|
if (typeof content === "string") {
|
||||||
const value = opts?.sanitizeText ? opts.sanitizeText(content) : content;
|
const value = opts?.sanitizeText ? opts.sanitizeText(content) : content;
|
||||||
@@ -32,6 +37,6 @@ export function extractTextFromChatContent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const joined = normalize(chunks.join(" "));
|
const joined = normalize(chunks.join(joinWith));
|
||||||
return joined ? joined : null;
|
return joined ? joined : null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,22 @@ describe("extractTextFromChatContent", () => {
|
|||||||
}),
|
}),
|
||||||
).toBe("Here ok");
|
).toBe("Here ok");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("supports custom join and normalization", () => {
|
||||||
|
expect(
|
||||||
|
extractTextFromChatContent(
|
||||||
|
[
|
||||||
|
{ type: "text", text: " hello " },
|
||||||
|
{ type: "text", text: "world " },
|
||||||
|
],
|
||||||
|
{
|
||||||
|
sanitizeText: (text) => text.trim(),
|
||||||
|
joinWith: "\n",
|
||||||
|
normalizeText: (text) => text.trim(),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
).toBe("hello\nworld");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("shared/frontmatter", () => {
|
describe("shared/frontmatter", () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user