Something went wrong. Try again.
snapcompact for prime agent
Something went wrong. Try again.
28 kB · 526 lines
JavaScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527import { pathToFileURL, fileURLToPath } from "node:url";import { createHash } from "node:crypto";import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";import { join } from "node:path";import { tmpdir } from "node:os";import { resolvePrimeRoot } from "./prime-root.mjs";
const primeRoot = resolvePrimeRoot();const { loadExtensions } = await import(pathToFileURL(join(primeRoot, "dist/core/extensions/loader.js")));const ai = await import(pathToFileURL(join( primeRoot, "node_modules/@earendil-works/pi-ai/dist/index.js",)));const extensionPath = fileURLToPath(new URL("../index.ts", import.meta.url));const root = mkdtempSync(join(tmpdir(), "prime-snapcompact-integration-"));const sessionDir = `${root}/sessions/project`;mkdirSync(sessionDir, { recursive: true });let activeSessionId = "11111111-1111-4111-8111-111111111111";let activeSessionFile = `${sessionDir}/${activeSessionId}.jsonl`;writeFileSync(activeSessionFile, "");const model = { id: "gpt-5.2", name: "test vision model", api: "openai-responses", provider: "openai", baseUrl: "https://example.invalid", reasoning: true, input: ["text", "image"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 272000, maxTokens: 16384,};const longText = "日本語の履歴と解析結果です。ファイルを確認しました。\n".repeat(6000);const usage = { input: 100, output: 100, cacheRead: 0, cacheWrite: 0, totalTokens: 200, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } };const oldMessages = [ { role: "user", content: "inspect the repository", timestamp: 1 }, { role: "assistant", content: [{ type: "text", text: longText }], api: model.api, provider: model.provider, model: model.id, usage, stopReason: "stop", timestamp: 2 },];const prep = (messages, previousSummary) => ({ firstKeptEntryId: "kept-entry", messagesToSummarize: messages, turnPrefixMessages: [], isSplitTurn: false, tokensBefore: 100000, previousSummary, fileOps: { read: new Set(["src/file.ts"]), written: new Set(), edited: new Set() }, settings: { enabled: true, reserveTokens: 16384, keepRecentTokens: 20000 },});const controller = new AbortController();const notifications = [];// Independent recomputation of the estimates the extension reports, so a notice// with a hardcoded or stale number cannot pass this suite.const plainText = (text) => text.replace(/[\u000e\u000f]/g, "").replaceAll("\u2588", "\n");const conservativeTextTokens = (text) => { let nonAscii = 0; for (let index = 0; index < text.length; index++) { if (text.charCodeAt(index) > 0x7f) nonAscii++; } return Math.ceil((text.length - nonAscii) / 3 + nonAscii * 2);};const noticeNumbers = (value) => Number(value.replaceAll(",", ""));const migrationEstimate = (message) => { const match = /about ([\d,]+) input tokens \(archive ([\d,]+), current messages ([\d,]+)\) against a ([\d,]+)-token budget; carrying ([\d,]+) of the archive's ([\d,]+) text tokens(?: and dropping ([\d,]+) of them from this request)?/.exec(message ?? ""); if (!match) return undefined; return { total: noticeNumbers(match[1]), archive: noticeNumbers(match[2]), messages: noticeNumbers(match[3]), budget: noticeNumbers(match[4]), carried: noticeNumbers(match[5]), archiveTotal: noticeNumbers(match[6]), dropped: match[7] === undefined ? 0 : noticeNumbers(match[7]), };};const archiveSourceTokens = (sessionId, ref) => { const artifact = JSON.parse(readFileSync( join(root, "sessions", "session-artifacts", sessionId, "snapcompact", ref.file), "utf8", )); return conservativeTextTokens(plainText(artifact.preserveData.snapcompact.text));};let branch = [];const ctx = { cwd: process.cwd(), hasUI: true, ui: { notify(message, level) { notifications.push({ message, level }); } }, model: { ...model }, modelRegistry: { async getApiKeyAndHeaders(targetModel) { if (targetModel?.provider === "faux") { return { ok: true, apiKey: "faux-key" }; } throw new Error("auth should not be needed in this test"); }, }, sessionManager: { getSessionFile: () => activeSessionFile, getSessionDir: () => sessionDir, getSessionId: () => activeSessionId, getBranch: () => branch, getEntries: () => branch, }, getSystemPrompt: () => "test system prompt", compact: () => { throw new Error("context hook must not compact reentrantly"); },};
const loaded = await loadExtensions([extensionPath], process.cwd());if (loaded.errors.length) throw new Error(JSON.stringify(loaded.errors));loaded.runtime.getThinkingLevel = () => "medium";const extension = loaded.extensions[0];const before = extension.handlers.get("session_before_compact")[0];const context = extension.handlers.get("context")[0];const sessionStart = extension.handlers.get("session_start")[0];const sessionCompact = extension.handlers.get("session_compact")[0];
ctx.model = { ...model, input: ["text"] };const initialText = await before({ type: "session_before_compact", preparation: prep(oldMessages), branchEntries: [], customInstructions: undefined, signal: controller.signal }, ctx);if (initialText !== undefined) throw new Error("initial text-only compaction did not defer to Prime native compaction");ctx.model = { ...model };const initialCustom = await before({ type: "session_before_compact", preparation: prep(oldMessages), branchEntries: [], customInstructions: "focus on tests", signal: controller.signal }, ctx);if (initialCustom !== undefined) throw new Error("initial custom-instruction compaction did not defer to Prime native compaction");const originalWarn = console.warn;console.warn = () => {};process.env.PRIME_SNAPCOMPACT_BUN = "/definitely/missing/bun";const initialFailure = await before({ type: "session_before_compact", preparation: prep(oldMessages), branchEntries: [], customInstructions: undefined, signal: controller.signal }, ctx);delete process.env.PRIME_SNAPCOMPACT_BUN;console.warn = originalWarn;if (initialFailure !== undefined) throw new Error("first worker failure did not fall back to Prime native compaction");
const first = await before({ type: "session_before_compact", preparation: prep(oldMessages), branchEntries: branch, customInstructions: undefined, signal: controller.signal }, ctx);if (!first?.compaction?.details?.snapcompact?.artifact) throw new Error("first SnapCompact compaction missing artifact reference");const detailsJson = JSON.stringify(first.compaction.details);if (Buffer.byteLength(detailsJson) >= 1024 * 1024) throw new Error("compaction details exceed Prime line safety limit");if (detailsJson.includes("iVBORw0KGgo")) throw new Error("PNG data leaked into compaction details");const ref1 = first.compaction.details.snapcompact.artifact;const compactionEntry = { type: "compaction", id: "compaction-1", parentId: null, timestamp: Date.now(), summary: first.compaction.summary, firstKeptEntryId: first.compaction.firstKeptEntryId, tokensBefore: first.compaction.tokensBefore, details: first.compaction.details, fromHook: true,};branch = [compactionEntry, { type: "message", id: "recent-1", parentId: "compaction-1", timestamp: Date.now(), message: { role: "user", content: "continue", timestamp: Date.now() } }];const hydrated = await context({ type: "context", messages: [ { role: "compactionSummary", summary: first.compaction.summary, tokensBefore: first.compaction.tokensBefore, timestamp: Date.now() }, { role: "user", content: "continue", timestamp: Date.now() },]}, ctx);const visual = hydrated.messages[0];const images = visual.content.filter((block) => block.type === "image");if (visual.role !== "custom" || images.length < 1 || images.length > first.compaction.details.snapcompact.frameBudget) { throw new Error("vision context did not rehydrate within frame budget");}if (images.some((image) => image.detail !== "original")) { throw new Error("vision context dropped SnapCompact frame detail");}// A different vision model reads the same frozen epoch: model identity never// gates the archive (the oh-my-pi contract), so the frames render unclamped and// no cross-model degradation notice appears.const switchBaseline = notifications.length;ctx.model = { ...model, id: "different-model-same-provider" };const switched = await context({ type: "context", messages: [ { role: "compactionSummary", summary: first.compaction.summary, tokensBefore: first.compaction.tokensBefore, timestamp: Date.now() }, { role: "user", content: "continue after model switch", timestamp: Date.now() },]}, ctx);const switchedImages = switched.messages[0].content.filter((block) => block.type === "image").length;if (switchedImages !== images.length) { throw new Error(`model switch changed the frame budget: ${switchedImages} of ${images.length} frames`);}if (notifications.length !== switchBaseline) { throw new Error(`model switch emitted a degradation notice: ${JSON.stringify(notifications.slice(switchBaseline))}`);}// Lane flip: the same model id and api served from another provider lane (as in// zai/glm-5.3-flash and hyper/glm-5.3-flash) is still the same model. The archive// frames are images of text, so a lane flip must not report cross-model image// support as unverified, must not clamp the frame budget, and must not force the// next compaction into the text path.ctx.model = { ...model, provider: "hyper" };const laneFlipBaseline = notifications.length;const laneFlipped = await context({ type: "context", messages: [ { role: "compactionSummary", summary: first.compaction.summary, tokensBefore: first.compaction.tokensBefore, timestamp: Date.now() }, { role: "user", content: "continue on the other provider lane", timestamp: Date.now() },]}, ctx);const laneFlipNotices = notifications.slice(laneFlipBaseline);const laneFlipDegradation = laneFlipNotices.find((n) => /different model|cross-model/.test(n.message));const laneFlipImages = laneFlipped.messages[0].content.filter((block) => block.type === "image").length;const laneFlipCompaction = await before({ type: "session_before_compact", preparation: prep(oldMessages, first.compaction.summary), branchEntries: branch, customInstructions: undefined, signal: controller.signal }, ctx);ctx.model = { ...model };const laneFlipFailures = [];if (laneFlipDegradation) { laneFlipFailures.push(`reported a cross-model degradation: ${JSON.stringify(laneFlipDegradation)}`);}if (laneFlipImages !== images.length) { laneFlipFailures.push(`clamped the frame budget: ${laneFlipImages} of ${images.length} frames`);}const laneFlipMigration = notifications.slice(laneFlipBaseline).find((n) => /image archive to text compaction/.test(n.message));if (laneFlipMigration) { laneFlipFailures.push(`started a text migration: ${laneFlipMigration.message}`);}if (laneFlipCompaction?.compaction?.details?.snapcompactText) { laneFlipFailures.push("forced the archive into the text path");} else if (laneFlipCompaction?.compaction?.details?.snapcompact?.producer?.provider !== "hyper") { laneFlipFailures.push("did not stay on the visual compaction path");}if (laneFlipFailures.length > 0) { throw new Error(`provider lane flip ${laneFlipFailures.join("; ")}`);}const steadyStateNoticeBaseline = notifications.length;
// Frozen epoch layout: block bytes must not depend on the live window or the// growing tail. Every rehydration of the same archive must be byte-identical,// otherwise each change busts the prompt cache back to the system prefix.const blockSignature = (content) => createHash("sha256").update(JSON.stringify(content)).digest("hex");let frozenSignature = blockSignature(visual.content);for (let turn = 0; turn < 20; turn++) { ctx.model = turn % 2 === 0 ? { ...model, contextWindow: 10_000, maxTokens: 8192 } : { ...model }; const turnHydrated = await context({ type: "context", messages: [ { role: "compactionSummary", summary: first.compaction.summary, tokensBefore: first.compaction.tokensBefore, timestamp: Date.now() }, ...Array.from({ length: turn }, (_, i) => ({ role: "user", content: `tail ${i} ${longText.slice(0, 400)}`, timestamp: Date.now() + i })), ]}, ctx); const signature = blockSignature(turnHydrated.messages[0].content); if (signature !== frozenSignature) { throw new Error(`vision archive blocks changed at tail step ${turn}; prompt cache would bust`); }}if (notifications.length !== steadyStateNoticeBaseline) { throw new Error("degradation notice re-emitted during steady-state turns for the same compaction");}ctx.model = { ...model };ctx.model = { ...model, input: ["text"], contextWindow: 10_000, maxTokens: 8192 };const exhaustedText = await context({ type: "context", messages: [ { role: "compactionSummary", summary: first.compaction.summary, tokensBefore: first.compaction.tokensBefore, timestamp: Date.now() }, { role: "user", content: "text context is already full", timestamp: Date.now() },]}, ctx);if (exhaustedText.messages[0].content.length !== 1) { throw new Error("exhausted text context forced archive text past zero budget");}ctx.model = { ...model };
const previousSessionFile = activeSessionFile;const forkSessionId = "22222222-2222-4222-8222-222222222222";activeSessionId = forkSessionId;activeSessionFile = `${sessionDir}/${activeSessionId}.jsonl`;writeFileSync(activeSessionFile, "");await sessionStart({ type: "session_start", reason: "fork", previousSessionFile }, ctx);const forkArtifact = join( root, "sessions", "session-artifacts", forkSessionId, "snapcompact", ref1.file,);if (!existsSync(forkArtifact)) throw new Error("fork did not copy its inherited SnapCompact artifact");const forkHydrated = await context({ type: "context", messages: [ { role: "compactionSummary", summary: first.compaction.summary, tokensBefore: first.compaction.tokensBefore, timestamp: Date.now() }, { role: "user", content: "continue fork", timestamp: Date.now() },]}, ctx);if (!forkHydrated.messages[0].content.some((block) => block.type === "image")) { throw new Error("forked context did not rehydrate copied archive images");}ctx.model = { ...model, input: ["text"] };const nonVisionBaseline = notifications.length;await context({ type: "context", messages: [ { role: "compactionSummary", summary: first.compaction.summary, tokensBefore: first.compaction.tokensBefore, timestamp: Date.now() }, { role: "user", content: "continue fork text", timestamp: Date.now() },]}, ctx);const nonVisionNotices = notifications.slice(nonVisionBaseline);if (nonVisionNotices.length !== 1 || nonVisionNotices[0].level !== "warning" || !nonVisionNotices[0].message.includes("this model cannot read images")) { throw new Error(`expected one non-vision degradation warning, got ${JSON.stringify(nonVisionNotices)}`);}await context({ type: "context", messages: [ { role: "compactionSummary", summary: first.compaction.summary, tokensBefore: first.compaction.tokensBefore, timestamp: Date.now() }, { role: "user", content: "second fork text turn", timestamp: Date.now() },]}, ctx);if (notifications.length !== nonVisionBaseline + 1) { throw new Error("non-vision degradation notice re-emitted on the second turn of the same compaction");}ctx.model = { ...model };
const sourceArtifact = join( root, "sessions", "session-artifacts", "11111111-1111-4111-8111-111111111111", "snapcompact", ref1.file,);rmSync(sourceArtifact, { force: true });activeSessionId = "33333333-3333-4333-8333-333333333333";activeSessionFile = `${sessionDir}/${activeSessionId}.jsonl`;writeFileSync(activeSessionFile, "");const missingSourceWarnings = [];console.warn = (...args) => missingSourceWarnings.push(args);try { await sessionStart({ type: "session_start", reason: "fork", previousSessionFile }, ctx);} finally { console.warn = originalWarn;}if (missingSourceWarnings.length > 0) { throw new Error("fork warned after its inherited artifact disappeared");}activeSessionId = forkSessionId;activeSessionFile = `${sessionDir}/${activeSessionId}.jsonl`;
const orphan = join(forkArtifact, "..", "33333333-3333-4333-8333-333333333333.json");writeFileSync(orphan, "orphan", { mode: 0o600 });await sessionCompact({ type: "session_compact", compactionEntry, fromExtension: true }, ctx);if (existsSync(orphan)) throw new Error("artifact cleanup did not remove an unreferenced artifact");if (!existsSync(forkArtifact)) throw new Error("artifact cleanup removed a referenced fork artifact");
const nextMessages = [ { role: "user", content: "implement and test the fix", timestamp: 3 }, { role: "assistant", content: [{ type: "text", text: "test passed\n".repeat(3000) }], api: model.api, provider: model.provider, model: model.id, usage, stopReason: "stop", timestamp: 4 },];const second = await before({ type: "session_before_compact", preparation: prep(nextMessages, first.compaction.summary), branchEntries: branch, customInstructions: undefined, signal: controller.signal }, ctx);if (!second?.compaction?.details?.snapcompact?.artifact) throw new Error("repeat compaction missing artifact");if (second.compaction.details.snapcompact.artifact.sha256 === ref1.sha256) throw new Error("repeat compaction did not create a new archive");if (JSON.stringify(second.compaction.details).includes("iVBORw0KGgo")) throw new Error("repeat details inlined PNG data");const secondArtifactPath = join( root, "sessions", "session-artifacts", activeSessionId, "snapcompact", second.compaction.details.snapcompact.artifact.file,);const secondArchive = JSON.parse(readFileSync(secondArtifactPath, "utf8")).preserveData.snapcompact;if (secondArchive.frames.some((frame) => frame.font !== "silver")) { throw new Error("repeat compaction did not retain the prior non-Latin-safe render shape");}
ctx.model = { ...model, input: ["text"], contextWindow: 128000 };const textHydrated = await context({ type: "context", messages: [ { role: "compactionSummary", summary: first.compaction.summary, tokensBefore: first.compaction.tokensBefore, timestamp: Date.now() }, { role: "user", content: "continue in text", timestamp: Date.now() },]}, ctx);if (textHydrated.messages[0].content.some((block) => block.type === "image")) throw new Error("text-only context contains images");const textChars = textHydrated.messages[0].content.reduce((n, block) => n + (block.type === "text" ? block.text.length : 0), 0);if (textChars > 128000 * 3) throw new Error("text-only context was not bounded");
ctx.model = { ...model, contextWindow: 20000, maxTokens: 4096 };console.warn = () => {};const workerFailureWithArchive = await before({ type: "session_before_compact", preparation: prep(nextMessages, first.compaction.summary), branchEntries: branch, customInstructions: undefined, signal: controller.signal }, ctx);console.warn = originalWarn;if (workerFailureWithArchive?.cancel !== true) throw new Error("worker failure with an existing archive did not cancel safely");const retainedNotice = notifications.at(-1);if ( retainedNotice?.level !== "error" || !retainedNotice.message.includes("SnapCompact visual compaction failed: SnapCompact worker failed") || !retainedNotice.message.includes("maxArchiveTokens must be positive") || !retainedNotice.message.includes("text fallback also failed") || !retainedNotice.message.includes("existing archive was retained")) { throw new Error(`worker failure notice hid the primary error: ${JSON.stringify(retainedNotice)}`);}// Migration that cannot fit: the current messages alone exceed the safe input// budget, so the archive text cannot ride along. Compaction must still run, using// native text compaction without the archive, and say what it dropped.const overBudgetRegistration = ai.registerFauxProvider({ models: [{ id: "snapcompact-over-budget-text", name: "over-budget text migration test", input: ["text"], reasoning: false, contextWindow: 20_000, maxTokens: 8192, }], tokensPerSecond: 100_000,});const overBudgetModel = overBudgetRegistration.getModel();overBudgetRegistration.setResponses([ ai.fauxAssistantMessage("over-budget text migration summary"),]);ctx.model = overBudgetModel;const overBudgetBaseline = notifications.length;const expectedArchiveTokens = archiveSourceTokens(activeSessionId, ref1);const overBudget = await before({ type: "session_before_compact", preparation: prep(nextMessages, first.compaction.summary), branchEntries: branch, customInstructions: "focus on tests", signal: controller.signal }, ctx);if (overBudget?.cancel === true) { throw new Error("over-budget text migration cancelled compaction instead of falling back");}if (overBudget?.compaction?.details?.snapcompactText?.schema !== 1) { throw new Error("over-budget text migration did not compact through the native fallback");}const overBudgetNotices = notifications.slice(overBudgetBaseline);const progressNotice = overBudgetNotices.find((n) => /image archive to text compaction/.test(n.message));if (!progressNotice) { throw new Error(`over-budget text migration emitted no progress notice: ${JSON.stringify(overBudgetNotices)}`);}const overBudgetEstimate = migrationEstimate(progressNotice.message);if (!overBudgetEstimate) { throw new Error(`text migration progress notice carried no token estimate: ${progressNotice.message}`);}if (overBudgetEstimate.budget !== 0) { throw new Error(`over-budget migration reported budget ${overBudgetEstimate.budget}: ${progressNotice.message}`);}if (overBudgetEstimate.archiveTotal !== expectedArchiveTokens) { throw new Error(`text migration progress notice estimate ignored the archive: ${progressNotice.message}`);}if (overBudgetEstimate.carried !== 0 || overBudgetEstimate.dropped !== expectedArchiveTokens) { throw new Error(`over-budget notice reported carried ${overBudgetEstimate.carried} and dropped ${overBudgetEstimate.dropped} of ${expectedArchiveTokens}: ${progressNotice.message}`);}if (!(overBudgetEstimate.total > overBudgetEstimate.messages && overBudgetEstimate.total < overBudgetEstimate.messages + expectedArchiveTokens)) { throw new Error(`text migration progress notice reported an implausible request size of ${overBudgetEstimate.total}: ${progressNotice.message}`);}const droppedNotice = overBudgetNotices.find( (n) => n.level === "warning" && /cannot fit the archive's [\d,]+ text tokens/.test(n.message),);if (!droppedNotice) { throw new Error(`over-budget migration did not state that the archive was dropped: ${JSON.stringify(overBudgetNotices)}`);}overBudgetRegistration.unregister();ctx.model = { ...model };
const largeRegistration = ai.registerFauxProvider({ models: [{ id: "snapcompact-large-text", name: "large text migration test", input: ["text"], reasoning: false, contextWindow: 2_000_000, maxTokens: 8192, }], tokensPerSecond: 100_000,});const largeTextModel = largeRegistration.getModel();largeRegistration.setResponses([ ai.fauxAssistantMessage("large text migration summary"),]);ctx.model = largeTextModel;const largeMigration = await before({ type: "session_before_compact", preparation: prep(nextMessages, first.compaction.summary), branchEntries: branch, customInstructions: "focus on tests", signal: controller.signal }, ctx);if (!largeMigration?.compaction) throw new Error("large text migration failed to compact");if (largeMigration.compaction.details?.snapcompactText?.schema !== 1) { throw new Error("large text migration did not record snapcompactText details");}
const truncatedRegistration = ai.registerFauxProvider({ models: [{ id: "snapcompact-truncated-text", name: "truncated text migration test", input: ["text"], reasoning: false, contextWindow: 60_000, maxTokens: 8192, }], tokensPerSecond: 100_000,});const truncatedTextModel = truncatedRegistration.getModel();truncatedRegistration.setResponses([ ai.fauxAssistantMessage("truncated text migration summary"),]);ctx.model = truncatedTextModel;const truncatedMigration = await before({ type: "session_before_compact", preparation: prep(nextMessages, first.compaction.summary), branchEntries: branch, customInstructions: "focus on tests", signal: controller.signal }, ctx);if (!truncatedMigration?.compaction) throw new Error("truncated text migration failed to compact");if (truncatedMigration.compaction.details?.snapcompactText?.schema !== 1) { throw new Error("truncated text migration did not record snapcompactText details");}const truncatedEstimate = migrationEstimate(notifications.at(-1)?.message);if (!truncatedEstimate) { throw new Error(`truncated text migration emitted no token estimate: ${JSON.stringify(notifications.at(-1))}`);}if (truncatedEstimate.archiveTotal !== expectedArchiveTokens) { throw new Error(`truncated migration estimate ignored the archive: ${JSON.stringify(notifications.at(-1))}`);}if (truncatedEstimate.carried <= 0 || truncatedEstimate.dropped <= 0) { throw new Error(`truncated migration did not bound one call's archive carry: ${JSON.stringify(notifications.at(-1))}`);}if (truncatedEstimate.carried + truncatedEstimate.dropped !== truncatedEstimate.archiveTotal) { throw new Error(`truncated migration notice did not account for the dropped archive text: ${JSON.stringify(notifications.at(-1))}`);}
// A compaction under a different vision model stays on the visual path and// records the new model as the archive's producer; it never migrates to text.const crossModelRegistration = ai.registerFauxProvider({ models: [{ id: "snapcompact-cross-model-vision", name: "cross-model vision compaction test", input: ["text", "image"], reasoning: false, contextWindow: 2_000_000, maxTokens: 8192, }], tokensPerSecond: 100_000,});const crossModel = crossModelRegistration.getModel();crossModelRegistration.setResponses([ ai.fauxAssistantMessage("cross-model migration summary"),]);ctx.model = crossModel;const crossModelCompaction = await before({ type: "session_before_compact", preparation: prep(nextMessages, first.compaction.summary), branchEntries: branch, customInstructions: undefined, signal: controller.signal,}, ctx);if (!crossModelCompaction?.compaction) throw new Error("cross-model compaction failed to compact");if (crossModelCompaction.compaction.details?.snapcompactText) { throw new Error("cross-model compaction migrated the archive to text");}if (crossModelCompaction.compaction.details?.snapcompact?.producer?.id !== crossModel.id) { throw new Error("cross-model compaction did not stay on the visual path");}crossModelRegistration.unregister();
console.log(JSON.stringify({ firstDetailsBytes: Buffer.byteLength(detailsJson), firstFrames: images.length, switchedModelFrames: switchedImages, exhaustedBudgetArchiveBlocks: 0, forkArtifactCopied: true, missingForkArtifactIgnored: true, orphanGc: true, repeatFont: "silver", firstArtifactBytes: ref1.bytes, repeatArtifactBytes: second.compaction.details.snapcompact.artifact.bytes, textContextChars: textChars, firstFailureNativeFallback: initialFailure === undefined, existingFailureCancelled: workerFailureWithArchive.cancel, overBudgetFallback: overBudget.compaction.details.snapcompactText.schema, overBudgetDroppedNotice: Boolean(droppedNotice), laneFlipFrames: laneFlipImages, laneFlipCompactionPath: laneFlipCompaction?.compaction?.details?.snapcompact ? "visual" : "text", textMigrationTokensReported: expectedArchiveTokens, largeModelMigrationSuccess: Boolean(largeMigration?.compaction), truncatedModelMigrationSuccess: Boolean(truncatedMigration?.compaction), crossModelStayedVisual: crossModelCompaction?.compaction?.details?.snapcompact?.producer?.id,}, null, 2));rmSync(root, { recursive: true, force: true });