import { randomUUID } from "node:crypto"; import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; import type { AgentMessage } from "@earendil-works/pi-agent-core"; import type { CodexDeveloperMessageDetails } from "../developer-messages.ts"; export const CODEX_CONTEXT_WINDOW_MESSAGE_TYPE = "codex-context-window"; export const CONTEXT_WINDOW_COMPACTION_SUMMARY = "[Pi Codex context-window boundary; no conversation summary was generated.]"; export const CONTEXT_WINDOW_COMPACTION_STRATEGY = "codex-context-window"; export const CONTEXT_WINDOW_REMINDER_THRESHOLD = 6_144; export const CONTEXT_WINDOW_FALLBACK_BUFFER = 16_384; export type ContextManagementMessageKind = | "window" | "reminder" | "fallback"; export interface ContextWindowIdentity { firstWindowId: string; currentWindowId: string; previousWindowId?: string | undefined; windowNumber: number; } export interface CodexContextManagementMessageDetails extends CodexDeveloperMessageDetails { contextManagement: { protocol: 1; kind: ContextManagementMessageKind; firstWindowId: string; currentWindowId: string; previousWindowId?: string | undefined; trimPreviousWindow?: true | undefined; windowNumber: number; }; } export interface ContextWindowCompactionDetails { protocol: 1; strategy: typeof CONTEXT_WINDOW_COMPACTION_STRATEGY; windowId?: string | undefined; } const CONTEXT_WINDOW_GUIDANCE = ` Checkpoint the active request, known history IDs, decisions, progress, learnings and next steps in notes before new_context; no summary carries over. After rollover, read hinted notes. Use history only for a missing detail. `; export function renderContextWindowMessage( identity: ContextWindowIdentity, threadHint?: string, ): string { const lines = [ "", "Agent name: /root", `First context window id: ${identity.firstWindowId}`, `Current context window id: ${identity.currentWindowId}`, ]; if (identity.previousWindowId) lines.push(`Previous context window id: ${identity.previousWindowId}`); if (threadHint) lines.push(threadHint); lines.push(""); return `${CONTEXT_WINDOW_GUIDANCE}\n\n${lines.join("\n")}`; } export function renderContextWindowReminder(remainingTokens: number): string { return ` Only ${Math.max(0, Math.floor(remainingTokens))} context tokens remain. Checkpoint the active request, state and known history IDs in notes, then call new_context; no conversation summary carries over. `; } export const CONTEXT_WINDOW_FALLBACK_MESSAGE = ` Context exhausted. Do not continue or answer. Make exactly one notes write or append call that checkpoints the active request, state and known history IDs, then call new_context. Use no other tools before rollover. `; export function isCodexContextManagementMessageDetails( value: unknown, ): value is CodexContextManagementMessageDetails { if (!value || typeof value !== "object") return false; const details = value as Record; if ( details["protocol"] !== 1 || typeof details["id"] !== "string" || !details["id"] ) return false; const context = details["contextManagement"]; if (!context || typeof context !== "object") return false; const record = context as Record; return ( record["protocol"] === 1 && (record["kind"] === "window" || record["kind"] === "reminder" || record["kind"] === "fallback") && typeof record["firstWindowId"] === "string" && record["firstWindowId"] !== "" && typeof record["currentWindowId"] === "string" && record["currentWindowId"] !== "" && (record["previousWindowId"] === undefined || typeof record["previousWindowId"] === "string") && (record["trimPreviousWindow"] === undefined || record["trimPreviousWindow"] === true) && Number.isInteger(record["windowNumber"]) && (record["windowNumber"] as number) >= 0 ); } export function isContextWindowBoundary( message: AgentMessage, ): message is Extract & { details: CodexContextManagementMessageDetails; } { return ( message.role === "custom" && message.customType === CODEX_CONTEXT_WINDOW_MESSAGE_TYPE && isCodexContextManagementMessageDetails(message.details) && message.details.contextManagement.kind === "window" ); } export function isContextWindowCompactionDetails( value: unknown, ): value is ContextWindowCompactionDetails { return Boolean( value && typeof value === "object" && "protocol" in value && value.protocol === 1 && "strategy" in value && value.strategy === CONTEXT_WINDOW_COMPACTION_STRATEGY, ); } export function sendContextWindowMessage( pi: ExtensionAPI, content: string, kind: ContextManagementMessageKind, identity: ContextWindowIdentity, options: { triggerTurn: boolean }, trimPreviousWindow = false, ): void { pi.sendMessage( { customType: CODEX_CONTEXT_WINDOW_MESSAGE_TYPE, content, display: true, details: { protocol: 1, id: randomUUID(), contextManagement: { protocol: 1, kind, ...identity, ...(trimPreviousWindow ? { trimPreviousWindow: true as const } : {}), }, }, }, options.triggerTurn ? { deliverAs: "steer", triggerTurn: true } : { triggerTurn: false }, ); }