import type { SessionEntry } from "@earendil-works/pi-coding-agent"; import type { Api, Model } from "@earendil-works/pi-ai"; import { providerHistory, type GrammarToolInputProperties } from "../compaction-checkpoint.ts"; import type { ImageDetail } from "../config.ts"; import { normalizeReplayItem, stableResponsesJson } from "../responses-replay.ts"; import type { ResponsesInputItem, ResponsesOutputItem } from "../responses-item-schema.ts"; import type { ResponsesItem as SerializedResponsesItem } from "../vendor/pi-ai/openai-responses-serialization.ts"; export function nativeOverrideRequired( rawItems: readonly ResponsesOutputItem[], canonicalItems: readonly SerializedResponsesItem[], ): boolean { if (rawItems.length !== canonicalItems.length) return true; return rawItems.some((item, index) => { const canonicalItem = canonicalItems[index]; return ( canonicalItem === undefined || stableResponsesJson(normalizeReplayItem(item)) !== stableResponsesJson(canonicalItem) ); }); } export function userEntryAfterLastSampled( branch: readonly SessionEntry[], ): SessionEntry | undefined { const lastSampledIndex = branch.findLastIndex( (entry) => entry.type === "message" && (entry.message.role === "assistant" || entry.message.role === "toolResult"), ); return branch .slice(lastSampledIndex + 1) .find((entry) => entry.type === "message" && entry.message.role === "user"); } export function splitUnsampledUserInput(options: { branch: readonly SessionEntry[]; history: readonly ResponsesInputItem[]; model: Model; grammarToolInputProperties: GrammarToolInputProperties; imageDetail: ImageDetail; }): | { kind: "none" | "found"; history: ResponsesInputItem[]; tail: ResponsesInputItem[] } | { kind: "unsafe" } { const firstUnsampled = userEntryAfterLastSampled(options.branch); if (!firstUnsampled) { return { kind: "none", history: options.history.map((item) => structuredClone(item)), tail: [], }; } const unsampledIndex = options.branch.findIndex((entry) => entry.id === firstUnsampled.id); const encoded = providerHistory({ branch: options.branch.slice(unsampledIndex), wireModel: options.model, grammarToolInputProperties: options.grammarToolInputProperties, imageDetail: options.imageDetail, }); if (encoded.length === 0 || encoded.length > options.history.length) return { kind: "unsafe" }; const splitIndex = options.history.length - encoded.length; if (JSON.stringify(options.history.slice(splitIndex)) !== JSON.stringify(encoded)) { return { kind: "unsafe" }; } return { kind: "found", history: options.history.slice(0, splitIndex).map((item) => structuredClone(item)), tail: encoded.map((item) => structuredClone(item)), }; }