import type { AgentMessage } from "@mariozechner/pi-agent-core"; import type { ImageContent, TextContent, ToolResultMessage } from "@mariozechner/pi-ai"; import type { ExtensionContext } from "@mariozechner/pi-coding-agent"; import { CHARS_PER_TOKEN_ESTIMATE, estimateStringChars } from "../../../utils/cjk-chars.js"; import { dropThinkingBlocks } from "../../pi-embedded-runner/thinking.js"; import type { EffectiveContextPruningSettings } from "./settings.js"; import { makeToolPrunablePredicate } from "./tools.js";
function collectTextSegments(content: ReadonlyArray<TextContent | ImageContent>): string[] { const parts: string[] = []; for (const block of content) { const text = coerceTextBlock(block); if (text !== null) {
parts.push(text);
}
} return parts;
}
function collectPrunableToolResultSegments(
content: ReadonlyArray<TextContent | ImageContent>,
): string[] { const parts: string[] = []; for (const block of content) { const text = coerceTextBlock(block); if (text !== null) {
parts.push(text); continue;
} if (isImageBlock(block)) {
parts.push(PRUNED_CONTEXT_IMAGE_MARKER);
}
} return parts;
}
function estimateJoinedTextLength(parts: string[]): number { if (parts.length === 0) { return0;
}
let len = 0; for (const p of parts) {
len += p.length;
} // Joined with "\n" separators between blocks.
len += Math.max(0, parts.length - 1); return len;
}
function takeHeadFromJoinedText(parts: string[], maxChars: number): string { if (maxChars <= 0 || parts.length === 0) { return"";
}
let remaining = maxChars;
let out = ""; for (let i = 0; i < parts.length && remaining > 0; i++) { if (i > 0) {
out += "\n";
remaining -= 1; if (remaining <= 0) { break;
}
} const p = parts[i]; if (p.length <= remaining) {
out += p;
remaining -= p.length;
} else {
out += p.slice(0, remaining);
remaining = 0;
}
} return out;
}
function takeTailFromJoinedText(parts: string[], maxChars: number): string { if (maxChars <= 0 || parts.length === 0) { return"";
}
let remaining = maxChars; const out: string[] = []; for (let i = parts.length - 1; i >= 0 && remaining > 0; i--) { const p = parts[i]; if (p.length <= remaining) {
out.push(p);
remaining -= p.length;
} else {
out.push(p.slice(p.length - remaining));
remaining = 0; break;
} if (remaining > 0 && i > 0) {
out.push("\n");
remaining -= 1;
}
}
out.reverse(); return out.join("");
}
function hasImageBlocks(content: ReadonlyArray<TextContent | ImageContent>): boolean { for (const block of content) { if (isImageBlock(block)) { returntrue;
}
} returnfalse;
}
function estimateWeightedTextChars(text: string): number { return estimateStringChars(text);
}
function estimateTextAndImageChars(content: ReadonlyArray<TextContent | ImageContent>): number {
let chars = 0; for (const block of content) { const text = coerceTextBlock(block); if (text !== null) {
chars += estimateWeightedTextChars(text); continue;
} if (isImageBlock(block)) {
chars += IMAGE_CHAR_ESTIMATE;
}
} return chars;
}
function estimateMessageChars(message: AgentMessage): number { if (message.role === "user") { const content = message.content; if (typeof content === "string") { return estimateWeightedTextChars(content);
} return estimateTextAndImageChars(content);
}
if (message.role === "assistant") {
let chars = 0; for (const b of message.content) { if (!b || typeof b !== "object") { continue;
} if (b.type === "text" && typeof b.text === "string") {
chars += estimateWeightedTextChars(b.text);
} const blockType = (b as { type?: unknown }).type; if (blockType === "thinking" || blockType === "redacted_thinking") { const thinking = (b as { thinking?: unknown }).thinking; if (typeof thinking === "string") {
chars += estimateWeightedTextChars(thinking);
} const data = (b as { data?: unknown }).data; if (blockType === "redacted_thinking" && typeof data === "string") {
chars += estimateWeightedTextChars(data);
} const signature = (b as { thinkingSignature?: unknown }).thinkingSignature; if (typeof signature === "string") {
chars += estimateWeightedTextChars(signature);
}
} if (b.type === "toolCall") { try {
chars += JSON.stringify(b.arguments ?? {}).length;
} catch {
chars += 128;
}
}
} return chars;
}
if (message.role === "toolResult") { return estimateTextAndImageChars(message.content);
}
return256;
}
function estimateContextChars(messages: AgentMessage[]): number { return messages.reduce((sum, m) => sum + estimateMessageChars(m), 0);
}
function findAssistantCutoffIndex(
messages: AgentMessage[],
keepLastAssistants: number,
): number | null { // keepLastAssistants <= 0 => everything is potentially prunable. if (keepLastAssistants <= 0) { return messages.length;
}
let remaining = keepLastAssistants; for (let i = messages.length - 1; i >= 0; i--) { if (messages[i]?.role !== "assistant") { continue;
}
remaining--; if (remaining === 0) { return i;
}
}
// Not enough assistant messages to establish a protected tail. returnnull;
}
function findFirstUserIndex(messages: AgentMessage[]): number | null { for (let i = 0; i < messages.length; i++) { if (messages[i]?.role === "user") { return i;
}
} returnnull;
}
// Bootstrap safety: never prune anything before the first user message. This protects initial // "identity" reads (SOUL.md, USER.md, etc.) which typically happen before the first inbound user // message exists in the session transcript. const firstUserIndex = findFirstUserIndex(messages); const pruneStartIndex = firstUserIndex === null ? messages.length : firstUserIndex;
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.