import type { AgentMessage } from "@mariozechner/pi-agent-core"; import type { AssistantMessage } from "@mariozechner/pi-ai"; import { extractTextFromChatContent } from "../shared/chat-content.js"; import {
normalizeAssistantPhase,
parseAssistantTextSignature,
type AssistantPhase,
} from "../shared/chat-message-content.js"; import { sanitizeAssistantVisibleText } from "../shared/text/assistant-visible-text.js"; import { stripReasoningTagsFromText } from "../shared/text/reasoning-tags.js"; import { sanitizeUserFacingText } from "./pi-embedded-helpers/sanitize-user-facing-text.js"; import { formatToolDetail, resolveToolDisplay } from "./tool-display.js";
export {
stripDowngradedToolCallText,
stripMinimaxToolCallXml,
} from "../shared/text/assistant-visible-text.js";
export { stripModelSpecialTokens } from "../shared/text/model-special-tokens.js";
export function isAssistantMessage(msg: AgentMessage | undefined): msg is AssistantMessage { return msg?.role === "assistant";
}
/** * Strip thinking tags and their content from text. * This is a safety net for cases where the model outputs <think> tags * that slip through other filtering mechanisms.
*/
export function stripThinkingTagsFromText(text: string): string { return stripReasoningTagsFromText(text, { mode: "strict", trim: "both" });
}
function sanitizeAssistantText(text: string): string { return sanitizeAssistantVisibleText(text);
}
export function extractAssistantText(msg: AssistantMessage): string { const extracted =
extractTextFromChatContent(msg.content, {
sanitizeText: (text) => sanitizeAssistantText(text),
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. // Gate on stopReason only — a non-error response with an errorMessage set (e.g. from a // background tool failure) should not have its content rewritten (#13935). return finalizeAssistantExtraction(msg, extracted);
}
export function extractAssistantThinking(msg: AssistantMessage): string { if (!Array.isArray(msg.content)) { return"";
} const blocks = msg.content
.map((block) => { if (!block || typeof block !== "object") { return"";
} const record = block as unknown as Record<string, unknown>; if (record.type === "thinking" && typeof record.thinking === "string") { const thinking = record.thinking.trim(); if (thinking) { return thinking;
} if (typeof record.thinkingSignature === "string" && record.thinkingSignature.trim()) { return"Native reasoning was produced; no summary text was returned.";
}
} return"";
})
.filter(Boolean); return blocks.join("\n").trim();
}
export function formatReasoningMessage(text: string): string { const trimmed = text.trim(); if (!trimmed) { return"";
} // Show reasoning in italics (cursive) for markdown-friendly surfaces (Discord, etc.). // Keep the plain "Reasoning:" prefix so existing parsing/detection keeps working. // Note: Underscore markdown cannot span multiple lines on Telegram, so we wrap // each non-empty line separately. const italicLines = trimmed
.split("\n")
.map((line) => (line ? `_${line}_` : line))
.join("\n"); return `Reasoning:\n${italicLines}`;
}
export function splitThinkingTaggedText(text: string): ThinkTaggedSplitBlock[] | null { const trimmedStart = text.trimStart(); // Avoid false positives: only treat it as structured thinking when it begins // with a think tag (common for local/OpenAI-compat providers that emulate // reasoning blocks via tags). if (!trimmedStart.startsWith("<")) { returnnull;
} const openRe = /<\s*(?:think(?:ing)?|thought|antthinking)\s*>/i; const closeRe = /<\s*\/\s*(?:think(?:ing)?|thought|antthinking)\s*>/i; if (!openRe.test(trimmedStart)) { returnnull;
} if (!closeRe.test(text)) { returnnull;
}
const scanRe = /<\s*(\/?)\s*(?:think(?:ing)?|thought|antthinking)\s*>/gi;
let inThinking = false;
let cursor = 0;
let thinkingStart = 0; const blocks: ThinkTaggedSplitBlock[] = [];
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.