refactor(shared): reuse chat content extractor for assistant text

This commit is contained in:
Peter Steinberger
2026-02-17 00:53:38 +00:00
parent ddef3cadba
commit f452a7a60b
4 changed files with 41 additions and 39 deletions

View File

@@ -1,5 +1,6 @@
import type { AgentMessage } from "@mariozechner/pi-agent-core";
import type { AssistantMessage } from "@mariozechner/pi-ai";
import { extractTextFromChatContent } from "../shared/chat-content.js";
import { stripReasoningTagsFromText } from "../shared/text/reasoning-tags.js";
import { sanitizeUserFacingText } from "./pi-embedded-helpers.js";
import { formatToolDetail, resolveToolDisplay } from "./tool-display.js";
@@ -207,25 +208,15 @@ export function stripThinkingTagsFromText(text: string): string {
}
export function extractAssistantText(msg: AssistantMessage): string {
const isTextBlock = (block: unknown): block is { type: "text"; text: string } => {
if (!block || typeof block !== "object") {
return false;
}
const rec = block as Record<string, unknown>;
return rec.type === "text" && typeof rec.text === "string";
};
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();
const extracted =
extractTextFromChatContent(msg.content, {
sanitizeText: (text) =>
stripThinkingTagsFromText(
stripDowngradedToolCallText(stripMinimaxToolCallXml(text)),
).trim(),
joinWith: "\n",
normalizeText: (text) => text.trim(),
}) ?? "";
// 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.
const errorContext = msg.stopReason === "error" || Boolean(msg.errorMessage?.trim());

View File

@@ -24,6 +24,7 @@ export {
resolveSessionReference,
shouldResolveSessionIdInput,
} from "./sessions-resolution.js";
import { extractTextFromChatContent } from "../../shared/chat-content.js";
import { sanitizeUserFacingText } from "../pi-embedded-helpers.js";
import {
stripDowngradedToolCallText,
@@ -152,23 +153,12 @@ export function extractAssistantText(message: unknown): string | undefined {
if (!Array.isArray(content)) {
return undefined;
}
const chunks: string[] = [];
for (const block of content) {
if (!block || typeof block !== "object") {
continue;
}
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 joined =
extractTextFromChatContent(content, {
sanitizeText: sanitizeTextContent,
joinWith: "",
normalizeText: (text) => text.trim(),
}) ?? "";
const stopReason = (message as { stopReason?: unknown }).stopReason;
const errorMessage = (message as { errorMessage?: unknown }).errorMessage;
const errorContext =

View File

@@ -1,8 +1,13 @@
export function extractTextFromChatContent(
content: unknown,
opts?: { sanitizeText?: (text: string) => string },
opts?: {
sanitizeText?: (text: string) => string;
joinWith?: string;
normalizeText?: (text: string) => string;
},
): 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") {
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;
}

View File

@@ -30,6 +30,22 @@ describe("extractTextFromChatContent", () => {
}),
).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", () => {