@oh-my-pi/snapcompact 18.10.3 → 18.10.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/dist/types/snapcompact.d.ts +40 -2
- package/package.json +6 -6
- package/src/snapcompact.ts +162 -33
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [18.10.4] - 2026-10-13
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added `viewWithoutThinking()`, `textWithoutThinking()`, `readerIncludesThinking()` and an optional `Archive.withoutThinking` variant, so an archive rendered with reasoning can be read by a model that must not see it; without a stored variant the view keeps only reasoning-free text and no frames ([#15436](https://github.com/can1357/oh-my-pi/pull/15436) by [@will-bogusz](https://github.com/will-bogusz))
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Fixed re-rendering an archive without reasoning keeping every paragraph after the first of a multi-paragraph `¶think:` section ([#15436](https://github.com/can1357/oh-my-pi/pull/15436) by [@will-bogusz](https://github.com/will-bogusz))
|
|
14
|
+
|
|
5
15
|
## [18.10.3] - 2026-10-12
|
|
6
16
|
|
|
7
17
|
### Breaking Changes
|
|
@@ -431,6 +431,15 @@ export interface Archive {
|
|
|
431
431
|
textHead?: string;
|
|
432
432
|
/** Newest text region kept verbatim around the imaged middle. */
|
|
433
433
|
textTail?: string;
|
|
434
|
+
/** The same history re-rendered for a reader that excludes assistant
|
|
435
|
+
* reasoning ({@link SerializeOptions.includeThinking} `false`), when this
|
|
436
|
+
* archive carries it. Read through {@link viewWithoutThinking}. */
|
|
437
|
+
withoutThinking?: ArchiveView;
|
|
438
|
+
}
|
|
439
|
+
/** What a reader gets for one compaction: the summary text and the archive it introduces. */
|
|
440
|
+
export interface ArchiveView {
|
|
441
|
+
summary: string;
|
|
442
|
+
archive: Archive;
|
|
434
443
|
}
|
|
435
444
|
export interface Geometry {
|
|
436
445
|
/** Characters per row (per-column line width when `columns === 2`). */
|
|
@@ -527,7 +536,8 @@ export interface SerializeOptions {
|
|
|
527
536
|
* louder than archived tool noise. Defaults to `true`. */
|
|
528
537
|
dimToolResults?: boolean;
|
|
529
538
|
/** Serialize assistant reasoning as `¶think:` sections. Defaults to `true`.
|
|
530
|
-
* Callers archiving for a
|
|
539
|
+
* Callers archiving for a reader whose classifier refuses quoted reasoning
|
|
540
|
+
* ({@link readerIncludesThinking} `false`) set this `false`:
|
|
531
541
|
* the archive frames are replayed as text into every later request, and
|
|
532
542
|
* reasoning rendered back to Claude trips its `reasoning_extraction`
|
|
533
543
|
* classifier (issue #6093). */
|
|
@@ -598,7 +608,8 @@ export declare function renderMany(text: string, options?: RenderManyOptions): P
|
|
|
598
608
|
* For doc shapes this wraps the text once and counts pages of `2 * rows`
|
|
599
609
|
* lines; for grid shapes it divides by the frame capacity. */
|
|
600
610
|
export declare function frames(text: string, options?: Pick<RenderManyOptions, "shape" | "model" | "frameSize">): number;
|
|
601
|
-
/** Validate and extract a persisted frame archive from `preserveData
|
|
611
|
+
/** Validate and extract a persisted frame archive from `preserveData`, with its
|
|
612
|
+
* {@link Archive.withoutThinking} variant when that is valid too. */
|
|
602
613
|
export declare function getPreservedArchive(preserveData: Record<string, unknown> | undefined): Archive | undefined;
|
|
603
614
|
/** Drop the persisted frame archive ({@link PRESERVE_KEY}) from `preserveData`,
|
|
604
615
|
* returning the remaining state — or `undefined` when nothing else remains, so
|
|
@@ -638,6 +649,33 @@ export declare function historyBlocks(archive: Archive, options?: HistoryBlockOp
|
|
|
638
649
|
* Returns `blocks` itself when every frame fits.
|
|
639
650
|
*/
|
|
640
651
|
export declare function capFrameDataBytes(blocks: (TextContent | ImageContent)[], maxFrameDataBytes: number): (TextContent | ImageContent)[];
|
|
652
|
+
/**
|
|
653
|
+
* Whether an archive read by `model` may carry assistant reasoning. The archive
|
|
654
|
+
* replays as text into every later request, and a reader whose classifier
|
|
655
|
+
* refuses reasoning quoted back into its input (the `classifier-cot-extraction`
|
|
656
|
+
* catalog axis, set for Claude: `reasoning_extraction`, issue #6093) gets
|
|
657
|
+
* `¶think:` sections and their legend left out.
|
|
658
|
+
*/
|
|
659
|
+
export declare function readerIncludesThinking(model: Model): boolean;
|
|
660
|
+
/** Whether a snapcompact summary declares `¶think:` reasoning scopes: true for
|
|
661
|
+
* archives rendered with {@link SerializeOptions.includeThinking}, and for
|
|
662
|
+
* summaries written before that legend was gated on it. */
|
|
663
|
+
export declare function summaryIncludesThinking(summary: string): boolean;
|
|
664
|
+
/** `text` (a summary or archive source, raw or normalized) without its
|
|
665
|
+
* `¶think:` sections, and with the reasoning scope dropped from any legend. */
|
|
666
|
+
export declare function textWithoutThinking(text: string): string;
|
|
667
|
+
/**
|
|
668
|
+
* What a reader that excludes assistant reasoning reads for a compaction
|
|
669
|
+
* whose summary is `summary` and archive `archive`: the archive's
|
|
670
|
+
* {@link Archive.withoutThinking} variant when it carries one, the original
|
|
671
|
+
* when its summary declares no reasoning, else a fail-closed text-only form.
|
|
672
|
+
* That form keeps no frames (pixels cannot be scrubbed), takes its text edges
|
|
673
|
+
* from the source after every `¶think:` section is removed (so a section
|
|
674
|
+
* crossing an edge cannot leak), counts the dropped middle as truncated, and
|
|
675
|
+
* re-renders the summary without the reasoning legend; `details` restores its
|
|
676
|
+
* file list.
|
|
677
|
+
*/
|
|
678
|
+
export declare function viewWithoutThinking(summary: string, archive: Archive, details?: CompactionDetails): ArchiveView;
|
|
641
679
|
/**
|
|
642
680
|
* Run a snapcompact compaction over prepared messages. Fully local: serializes
|
|
643
681
|
* the discarded history, appends it to the accumulated archive source text, and
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/snapcompact",
|
|
4
|
-
"version": "18.10.
|
|
4
|
+
"version": "18.10.4",
|
|
5
5
|
"description": "Bitmap-frame context compression for vision-capable LLMs",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": {
|
|
@@ -34,11 +34,11 @@
|
|
|
34
34
|
"fmt": "oxfmt --no-error-on-unmatched-pattern 'src/**/*.{ts,tsx}' '{test,bench,examples,scripts}/**/*.ts' '*.ts'"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@oh-my-pi/pi-ai": "18.10.
|
|
38
|
-
"@oh-my-pi/pi-catalog": "18.10.
|
|
39
|
-
"@oh-my-pi/pi-natives": "18.10.
|
|
40
|
-
"@oh-my-pi/pi-utils": "18.10.
|
|
41
|
-
"@oh-my-pi/pi-wire": "18.10.
|
|
37
|
+
"@oh-my-pi/pi-ai": "18.10.4",
|
|
38
|
+
"@oh-my-pi/pi-catalog": "18.10.4",
|
|
39
|
+
"@oh-my-pi/pi-natives": "18.10.4",
|
|
40
|
+
"@oh-my-pi/pi-utils": "18.10.4",
|
|
41
|
+
"@oh-my-pi/pi-wire": "18.10.4"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@types/bun": "^1.3.14"
|
package/src/snapcompact.ts
CHANGED
|
@@ -67,7 +67,7 @@ import {
|
|
|
67
67
|
import { resolveImageCountBudget, resolveInlineImageByteBudget } from "@oh-my-pi/pi-catalog/compat/request-size";
|
|
68
68
|
import { classifyModel, compareRevision, parseRevision } from "@oh-my-pi/pi-catalog/identity";
|
|
69
69
|
import { renderSnapcompactPng, snapcompactSupportedChars } from "@oh-my-pi/pi-natives";
|
|
70
|
-
import { formatGroupedPaths, prompt } from "@oh-my-pi/pi-utils";
|
|
70
|
+
import { formatGroupedPaths, isRecord, prompt } from "@oh-my-pi/pi-utils";
|
|
71
71
|
import { INTENT_FIELD } from "@oh-my-pi/pi-wire";
|
|
72
72
|
import fileOperationsTemplate from "./prompts/file-operations.md" with { type: "text" };
|
|
73
73
|
import snapcompactSummaryPrompt from "./prompts/snapcompact-summary.md" with { type: "text" };
|
|
@@ -644,6 +644,16 @@ export interface Archive {
|
|
|
644
644
|
textHead?: string;
|
|
645
645
|
/** Newest text region kept verbatim around the imaged middle. */
|
|
646
646
|
textTail?: string;
|
|
647
|
+
/** The same history re-rendered for a reader that excludes assistant
|
|
648
|
+
* reasoning ({@link SerializeOptions.includeThinking} `false`), when this
|
|
649
|
+
* archive carries it. Read through {@link viewWithoutThinking}. */
|
|
650
|
+
withoutThinking?: ArchiveView;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/** What a reader gets for one compaction: the summary text and the archive it introduces. */
|
|
654
|
+
export interface ArchiveView {
|
|
655
|
+
summary: string;
|
|
656
|
+
archive: Archive;
|
|
647
657
|
}
|
|
648
658
|
|
|
649
659
|
export interface Geometry {
|
|
@@ -835,7 +845,8 @@ export interface SerializeOptions {
|
|
|
835
845
|
* louder than archived tool noise. Defaults to `true`. */
|
|
836
846
|
dimToolResults?: boolean;
|
|
837
847
|
/** Serialize assistant reasoning as `¶think:` sections. Defaults to `true`.
|
|
838
|
-
* Callers archiving for a
|
|
848
|
+
* Callers archiving for a reader whose classifier refuses quoted reasoning
|
|
849
|
+
* ({@link readerIncludesThinking} `false`) set this `false`:
|
|
839
850
|
* the archive frames are replayed as text into every later request, and
|
|
840
851
|
* reasoning rendered back to Claude trips its `reasoning_extraction`
|
|
841
852
|
* classifier (issue #6093). */
|
|
@@ -1768,9 +1779,8 @@ export function frames(text: string, options?: Pick<RenderManyOptions, "shape" |
|
|
|
1768
1779
|
// Archive helpers
|
|
1769
1780
|
// ============================================================================
|
|
1770
1781
|
|
|
1771
|
-
/** Validate
|
|
1772
|
-
|
|
1773
|
-
const candidate = preserveData?.[PRESERVE_KEY];
|
|
1782
|
+
/** Validate one persisted archive object (frames, counters, text regions). */
|
|
1783
|
+
function validateArchive(candidate: unknown): Archive | undefined {
|
|
1774
1784
|
if (!candidate || typeof candidate !== "object") return undefined;
|
|
1775
1785
|
const archive = candidate as Archive;
|
|
1776
1786
|
const frames = Array.isArray(archive.frames)
|
|
@@ -1801,6 +1811,18 @@ export function getPreservedArchive(preserveData: Record<string, unknown> | unde
|
|
|
1801
1811
|
};
|
|
1802
1812
|
}
|
|
1803
1813
|
|
|
1814
|
+
/** Validate and extract a persisted frame archive from `preserveData`, with its
|
|
1815
|
+
* {@link Archive.withoutThinking} variant when that is valid too. */
|
|
1816
|
+
export function getPreservedArchive(preserveData: Record<string, unknown> | undefined): Archive | undefined {
|
|
1817
|
+
const slot = preserveData?.[PRESERVE_KEY];
|
|
1818
|
+
const archive = validateArchive(slot);
|
|
1819
|
+
if (!archive || !isRecord(slot) || !isRecord(slot.withoutThinking)) return archive;
|
|
1820
|
+
const { summary, archive: variant } = slot.withoutThinking;
|
|
1821
|
+
if (typeof summary !== "string" || !summary) return archive;
|
|
1822
|
+
const variantArchive = validateArchive(variant);
|
|
1823
|
+
return variantArchive ? { ...archive, withoutThinking: { summary, archive: variantArchive } } : archive;
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1804
1826
|
/** Drop the persisted frame archive ({@link PRESERVE_KEY}) from `preserveData`,
|
|
1805
1827
|
* returning the remaining state — or `undefined` when nothing else remains, so
|
|
1806
1828
|
* an empty `{}` is never persisted. Callers strip the archive once its frames
|
|
@@ -2184,29 +2206,133 @@ function planArchive(text: string, high: Shape, low: Shape, maxFrames: number):
|
|
|
2184
2206
|
};
|
|
2185
2207
|
}
|
|
2186
2208
|
|
|
2209
|
+
const THINK_LABEL = "¶think:";
|
|
2210
|
+
|
|
2211
|
+
/** A scope label opening a serialized section, at the start of a line in raw
|
|
2212
|
+
* (`\n`) or normalized ({@link NEWLINE_GLYPH}) archive text. */
|
|
2213
|
+
const SECTION_START = new RegExp(`(?<=^|[\\n${NEWLINE_GLYPH}])(?=¶(?:user|think|ai|call):)`);
|
|
2214
|
+
|
|
2215
|
+
const TRAILING_LINE_BREAKS = new RegExp(`[\\n${NEWLINE_GLYPH}]+$`);
|
|
2216
|
+
|
|
2187
2217
|
/**
|
|
2188
|
-
* Drop `¶think:`
|
|
2218
|
+
* Drop every `¶think:` section from archive source text: its label line and
|
|
2219
|
+
* every following line up to the next scope label.
|
|
2189
2220
|
*
|
|
2190
|
-
* Archives
|
|
2191
|
-
* reasoning into their kept source; replaying it to Claude trips
|
|
2192
|
-
* `reasoning_extraction` classifier (issue #6093). Re-
|
|
2193
|
-
*
|
|
2194
|
-
*
|
|
2195
|
-
*
|
|
2221
|
+
* Archives rendered with {@link SerializeOptions.includeThinking} (or before it
|
|
2222
|
+
* existed) bake reasoning into their kept source; replaying it to Claude trips
|
|
2223
|
+
* the `reasoning_extraction` classifier (issue #6093). Re-rendering for a reader
|
|
2224
|
+
* that excludes thinking scrubs the prior text with this. Paragraph breaks
|
|
2225
|
+
* inside a thought are line breaks like any other, so a section ends only at
|
|
2226
|
+
* the next label; a label inside a dimmed tool-output body is output text.
|
|
2196
2227
|
*/
|
|
2197
2228
|
function stripThinkingSections(text: string): string {
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
)
|
|
2206
|
-
|
|
2229
|
+
const sections: string[] = [];
|
|
2230
|
+
let dimOpen = false;
|
|
2231
|
+
for (const piece of text.split(SECTION_START)) {
|
|
2232
|
+
if (dimOpen) sections[sections.length - 1] += piece;
|
|
2233
|
+
else sections.push(piece);
|
|
2234
|
+
const on = piece.lastIndexOf(DIM_ON);
|
|
2235
|
+
const off = piece.lastIndexOf(DIM_OFF);
|
|
2236
|
+
if (on !== off) dimOpen = on > off;
|
|
2237
|
+
}
|
|
2238
|
+
const kept = sections.filter(section => !section.startsWith(THINK_LABEL));
|
|
2239
|
+
if (kept.length === sections.length) return text;
|
|
2240
|
+
const stripped = kept.join("");
|
|
2241
|
+
// A dropped final section leaves its predecessor's line break dangling.
|
|
2242
|
+
return sections.at(-1)?.startsWith(THINK_LABEL) ? stripped.replace(TRAILING_LINE_BREAKS, "") : stripped;
|
|
2243
|
+
}
|
|
2244
|
+
|
|
2245
|
+
/**
|
|
2246
|
+
* Whether an archive read by `model` may carry assistant reasoning. The archive
|
|
2247
|
+
* replays as text into every later request, and a reader whose classifier
|
|
2248
|
+
* refuses reasoning quoted back into its input (the `classifier-cot-extraction`
|
|
2249
|
+
* catalog axis, set for Claude: `reasoning_extraction`, issue #6093) gets
|
|
2250
|
+
* `¶think:` sections and their legend left out.
|
|
2251
|
+
*/
|
|
2252
|
+
export function readerIncludesThinking(model: Model): boolean {
|
|
2253
|
+
return resolveCatalogPolicy(model).classifierCotExtraction !== true;
|
|
2254
|
+
}
|
|
2255
|
+
|
|
2256
|
+
/** Whether a snapcompact summary declares `¶think:` reasoning scopes: true for
|
|
2257
|
+
* archives rendered with {@link SerializeOptions.includeThinking}, and for
|
|
2258
|
+
* summaries written before that legend was gated on it. */
|
|
2259
|
+
export function summaryIncludesThinking(summary: string): boolean {
|
|
2260
|
+
return summary.includes(THINK_LABEL);
|
|
2261
|
+
}
|
|
2262
|
+
|
|
2263
|
+
/** The reasoning scope in a scope legend, current and earlier summary wordings
|
|
2264
|
+
* alike, wherever it sits: a summary, or one archived inside another archive. */
|
|
2265
|
+
const LEGEND_REASONING_SCOPE =
|
|
2266
|
+
/`¶user:`, `¶think:`, (`¶ai:`, (?:and )?`¶call:`:? (?:open )?user, )assistant reasoning, /g;
|
|
2267
|
+
|
|
2268
|
+
/** `text` (a summary or archive source, raw or normalized) without its
|
|
2269
|
+
* `¶think:` sections, and with the reasoning scope dropped from any legend. */
|
|
2270
|
+
export function textWithoutThinking(text: string): string {
|
|
2271
|
+
return stripThinkingSections(text).replace(LEGEND_REASONING_SCOPE, "`¶user:`, $1");
|
|
2272
|
+
}
|
|
2273
|
+
|
|
2274
|
+
const PREVIOUS_SUMMARY_HEAD = "[Summary of earlier history]";
|
|
2275
|
+
|
|
2276
|
+
/**
|
|
2277
|
+
* `archive`'s kept source as a reader that excludes reasoning may unfold it.
|
|
2278
|
+
* An archive persisted without {@link Archive.text} keeps only its edges, and
|
|
2279
|
+
* the tail's lines before its first scope label continue a section cut off in
|
|
2280
|
+
* the dropped middle, whose scope is unknown; they are left out.
|
|
2281
|
+
*/
|
|
2282
|
+
function sourceForReaderWithoutThinking(archive: Archive): string {
|
|
2283
|
+
if (archive.text) return archive.text;
|
|
2284
|
+
const tail = archive.textTail ?? "";
|
|
2285
|
+
const labelled = tail.search(SECTION_START);
|
|
2286
|
+
return [archive.textHead, labelled < 0 ? "" : tail.slice(labelled)]
|
|
2287
|
+
.filter((part): part is string => typeof part === "string" && part.length > 0)
|
|
2207
2288
|
.join(NEWLINE_GLYPH);
|
|
2208
2289
|
}
|
|
2209
2290
|
|
|
2291
|
+
/**
|
|
2292
|
+
* What a reader that excludes assistant reasoning reads for a compaction
|
|
2293
|
+
* whose summary is `summary` and archive `archive`: the archive's
|
|
2294
|
+
* {@link Archive.withoutThinking} variant when it carries one, the original
|
|
2295
|
+
* when its summary declares no reasoning, else a fail-closed text-only form.
|
|
2296
|
+
* That form keeps no frames (pixels cannot be scrubbed), takes its text edges
|
|
2297
|
+
* from the source after every `¶think:` section is removed (so a section
|
|
2298
|
+
* crossing an edge cannot leak), counts the dropped middle as truncated, and
|
|
2299
|
+
* re-renders the summary without the reasoning legend; `details` restores its
|
|
2300
|
+
* file list.
|
|
2301
|
+
*/
|
|
2302
|
+
export function viewWithoutThinking(summary: string, archive: Archive, details?: CompactionDetails): ArchiveView {
|
|
2303
|
+
if (archive.withoutThinking) return archive.withoutThinking;
|
|
2304
|
+
if (!summaryIncludesThinking(summary)) return { summary, archive };
|
|
2305
|
+
const source = sourceForReaderWithoutThinking(archive);
|
|
2306
|
+
const text = textWithoutThinking(source);
|
|
2307
|
+
const headChars = archive.textHead?.length ?? 0;
|
|
2308
|
+
const tailChars = archive.textTail?.length ?? 0;
|
|
2309
|
+
const fits = headChars + tailChars >= text.length;
|
|
2310
|
+
const textHead = fits ? text : text.slice(0, headChars);
|
|
2311
|
+
const textTail = fits || tailChars === 0 ? "" : text.slice(text.length - tailChars);
|
|
2312
|
+
const truncatedChars = archive.truncatedChars + (fits ? 0 : text.length - headChars - tailChars);
|
|
2313
|
+
const files = details ? formatFileList(details.readFiles, details.modifiedFiles) : "";
|
|
2314
|
+
return {
|
|
2315
|
+
summary:
|
|
2316
|
+
textHead.length === 0 && files.length === 0
|
|
2317
|
+
? "No prior history."
|
|
2318
|
+
: prompt.render(snapcompactSummaryPrompt, {
|
|
2319
|
+
frameCount: 0,
|
|
2320
|
+
truncatedChars,
|
|
2321
|
+
includedPreviousSummary: text.startsWith(PREVIOUS_SUMMARY_HEAD),
|
|
2322
|
+
files: files.length > 0 ? files : undefined,
|
|
2323
|
+
includeThinking: false,
|
|
2324
|
+
}),
|
|
2325
|
+
archive: {
|
|
2326
|
+
frames: [],
|
|
2327
|
+
totalChars: textHead.length + textTail.length,
|
|
2328
|
+
truncatedChars,
|
|
2329
|
+
...(text ? { text } : {}),
|
|
2330
|
+
...(textHead ? { textHead } : {}),
|
|
2331
|
+
...(textTail ? { textTail } : {}),
|
|
2332
|
+
},
|
|
2333
|
+
};
|
|
2334
|
+
}
|
|
2335
|
+
|
|
2210
2336
|
/**
|
|
2211
2337
|
* Run a snapcompact compaction over prepared messages. Fully local: serializes
|
|
2212
2338
|
* the discarded history, appends it to the accumulated archive source text, and
|
|
@@ -2232,21 +2358,23 @@ export async function compact<TMessage = Message>(
|
|
|
2232
2358
|
const llmMessages = (options?.convertToLlm ?? defaultConvertToLlm)(messages);
|
|
2233
2359
|
const serialized = serializeConversation(llmMessages, options);
|
|
2234
2360
|
const previousArchive = getPreservedArchive(previousPreserveData);
|
|
2361
|
+
const excludeThinking = options?.includeThinking === false;
|
|
2235
2362
|
const previousTextRaw =
|
|
2236
|
-
previousArchive
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2363
|
+
excludeThinking && previousArchive
|
|
2364
|
+
? sourceForReaderWithoutThinking(previousArchive)
|
|
2365
|
+
: (previousArchive?.text ??
|
|
2366
|
+
[previousArchive?.textHead, previousArchive?.textTail]
|
|
2367
|
+
.filter((part): part is string => typeof part === "string" && part.length > 0)
|
|
2368
|
+
.join(NEWLINE_GLYPH));
|
|
2240
2369
|
// Legacy archives may carry `¶think:` sections from before includeThinking
|
|
2241
|
-
// existed
|
|
2242
|
-
//
|
|
2243
|
-
//
|
|
2244
|
-
//
|
|
2370
|
+
// existed, and any archive may carry an earlier summary's reasoning legend;
|
|
2371
|
+
// scrub both when this compaction excludes thinking so the re-rendered
|
|
2372
|
+
// archive stops replaying reasoning (issue #6093). They may also carry data
|
|
2373
|
+
// URLs a pre-guard slice cut at any offset; heal those in archive context
|
|
2374
|
+
// before the text is folded into the new source.
|
|
2245
2375
|
const previousTextHealed = elideDataUrls(previousTextRaw, "archive");
|
|
2246
2376
|
const previousText =
|
|
2247
|
-
|
|
2248
|
-
? stripThinkingSections(previousTextHealed)
|
|
2249
|
-
: previousTextHealed;
|
|
2377
|
+
excludeThinking && previousTextHealed.length > 0 ? textWithoutThinking(previousTextHealed) : previousTextHealed;
|
|
2250
2378
|
const hasPreviousText = previousText.length > 0;
|
|
2251
2379
|
const includedPreviousSummary = !hasPreviousText && !!previousSummary;
|
|
2252
2380
|
const shapeProbeText = renderabilityProbeText(serialized, previousPreserveData, previousSummary);
|
|
@@ -2262,7 +2390,8 @@ export async function compact<TMessage = Message>(
|
|
|
2262
2390
|
let archiveText = normalize(serialized, { shape: high });
|
|
2263
2391
|
|
|
2264
2392
|
if (includedPreviousSummary && previousSummary) {
|
|
2265
|
-
const
|
|
2393
|
+
const lead = excludeThinking ? textWithoutThinking(previousSummary) : previousSummary;
|
|
2394
|
+
const head = `${PREVIOUS_SUMMARY_HEAD} ${normalize(lead, { shape: high })}`;
|
|
2266
2395
|
archiveText = archiveText.length > 0 ? `${head} [Recent conversation] ${archiveText}` : head;
|
|
2267
2396
|
}
|
|
2268
2397
|
|