@prometheus-ai/snapcompact 0.5.8
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 +50 -0
- package/README.md +70 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/snapcompact.d.ts +523 -0
- package/package.json +64 -0
- package/src/index.ts +1 -0
- package/src/prompts/file-operations.md +5 -0
- package/src/prompts/snapcompact-summary.md +24 -0
- package/src/snapcompact.ts +1290 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [Unreleased]
|
|
4
|
+
|
|
5
|
+
## [0.5.5] - 2026-06-14
|
|
6
|
+
|
|
7
|
+
### Breaking Changes
|
|
8
|
+
|
|
9
|
+
- Renamed every export to drop the `snapcompact`/`Snapcompact`/`SNAPCOMPACT_` qualifier — the package is meant to be consumed via `import * as snapcompact from "@prometheus-ai/snapcompact"`. Functions: `snapcompactCompact` → `compact`, `renderSnapcompactFrame` → `render`, `snapcompactGeometry` → `geometry`, `normalizeForSnapcompact` → `normalize`, `serializeSnapcompactConversation` → `serializeConversation`, `snapcompactImages` → `images`, `getPreservedSnapcompactArchive` → `getPreservedArchive`, `isSnapcompactShape` → `isShape`, `resolveSnapcompactShape` → `resolveShape`, `createSnapcompactFileOps` → `createFileOps`, `computeSnapcompactFileLists` → `computeFileLists`, `upsertSnapcompactFileOperations` → `upsertFileOperations`. Types: `SnapcompactShape` → `Shape`, `SnapcompactFrame` → `Frame`, `SnapcompactArchive` → `Archive`, `SnapcompactGeometry` → `Geometry`, `SnapcompactOptions` → `Options`, `SnapcompactSerializeOptions` → `SerializeOptions`, `SnapcompactFileOperations` → `FileOperations`, `SnapcompactCompactionDetails`/`Preparation`/`Result` → `CompactionDetails`/`CompactionPreparation`/`CompactionResult`, `SnapcompactConvertToLlm` → `ConvertToLlm`. Constants: `SNAPCOMPACT_X` → `X` (`SHAPES`, `FRAME_SIZE`, `MAX_FRAMES`, `FRAME_TOKEN_ESTIMATE`, `PRESERVE_KEY`, `TOOL_RESULT_MAX_CHARS`, `TOOL_ARG_MAX_CHARS`, `TOOL_CALL_MAX_CHARS`, `TRUNCATE_HEAD_RATIO`, `DIM_ON`, `DIM_OFF`).
|
|
10
|
+
- Changed `renderSnapcompactFrame` output from `png: Uint8Array` to `data: string` base64, requiring consumers to read frame payloads from `frame.data`
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Added two spacing-tuned frame variants to `SHAPE_VARIANTS`: `8on22-bw` (8x13 glyphs on a 22px pitch — extra line spacing) and `11on16-bw` (8x13 glyphs on an 11px advance — extra letter spacing). Both pin the indexed `stretch: false` path, so the native renderer draws natural-size glyphs on the padded cell box (the Rust path already advances by `cellWidth` and the new variants validate horizontal padding)
|
|
15
|
+
- Added `SHAPE_VARIANTS`, the catalog of research-eval frame variants the native renderer reproduces faithfully (`8x8r`/`8x8u`/`6x6u`/`5x8` × `sent`/`bw`), with `ShapeVariantName`, `SHAPE_VARIANT_NAMES`, and the `isShapeVariantName` guard
|
|
16
|
+
- `resolveShape(api, variant?)` now accepts an explicit variant name (or `"auto"`); forced variants keep their geometry but are re-priced for the target provider's image billing (token estimate and OpenAI `original` detail hint)
|
|
17
|
+
- Added the six research-eval winning frame variants to `SHAPE_VARIANTS`: `6x12-dim` (Claude fable), `8x13-bw` (Opus), `8on16-bw` (GPT grid runner-up), `doc-8on16-bw` (GPT), `doc-8on16-sent` (GLM), and `doc-8on16-sent-dim` (Gemini/Kimi), backed by new `Shape` fields `stretch` (disable Lanczos stretch: natural glyphs on a larger cell pitch), `columns` (two word-wrapped newspaper columns), `stopwordDim`, and the X.org `6x12`/`8x13` fonts
|
|
18
|
+
- Added `dimStopwords()`, which prints high-frequency function words in dim ink via zero-width markers (skipping spans that are already dim), and `wrap()`, the greedy word-wrap used to typeset doc-layout pages; `geometry`/`render`/`renderMany`/`frames`/`compact` understand doc shapes (wrap once, paginate into `2 * rows`-line pages), and compaction frames persist `columns`/`stopwordDim` for mixed-shape detection
|
|
19
|
+
- `resolveShape` now takes a `ShapeTarget` (`{ api, id }` — a Prometheus AI `Model` works as-is) and detects the ideal shape from the **model id**, not just the wire API: a Claude routed through Vertex or an OpenAI-compatible gateway keeps its Claude shape, with billing still priced by the API family actually carrying the request. `idealShapeVariant(modelId)` exposes the model-line table; unmeasured models fall back to the API family's winner
|
|
20
|
+
- `resolveShape` now also resolves an ideal **frame size** per model line, and billing estimates come from verified per-family formulas instead of flat 1568px constants: Anthropic bills 28px patches capped at 4,784 visual tokens (+5% margin), Gemini 3.x bills a fixed 1,120-token `media_resolution` budget per image at any pixel size, and OpenAI bills 32px patches × 1.2 under the 10,000-patch `detail: "original"` budget. High-res Claude lines (Opus 4.7+, Fable, Mythos — native 2576px-edge ingestion) get 1932px frames (same recall and cost, a third fewer frames); Gemini gets 2048px frames (+70% chars per frame at the same bill); GPT and Kimi stay at 1568px (area-proportional billing and a model-side 1792px processor cap, respectively). `idealShapeVariant` now returns an `IdealShape` (`{ variant, frameSize? }`)
|
|
21
|
+
- Added per-provider image-count budgets: `PROVIDER_IMAGE_BUDGETS`, `DEFAULT_PROVIDER_IMAGE_BUDGET`, `providerImageBudget()`, and `providerFrameBudget()` (the image budget clamped to `MAX_FRAMES`). OpenRouter is capped at its measured hard limit of 8 images per request (excess images are silently dropped with no error); unknown providers get a safe floor of 5
|
|
22
|
+
- Added `Archive.textTail`: archive content past the frame budget is no longer dropped — `compact()` stops rendering at the budget and keeps the newest unframed slice as verbatim text on the summary (capped at two frame capacities with middle elision, counted into `truncatedChars` when elided). The tail persists in `preserveData` and is folded back into frames by the next compaction
|
|
23
|
+
- Added `renderMany()` for paging arbitrary text into snapcompact PNG frames as LLM image blocks, and `frames()` for predicting the frame count without rendering
|
|
24
|
+
- Added new serialization options `toolResultMaxChars`, `toolArgMaxChars`, `toolCallMaxChars`, `truncateHeadRatio`, and `dimToolResults` to `snapcompactCompact`/`serializeSnapcompactConversation` so callers can tune how tool results and arguments are archived
|
|
25
|
+
- Added exported default constants `SNAPCOMPACT_TOOL_RESULT_MAX_CHARS`, `SNAPCOMPACT_TOOL_ARG_MAX_CHARS`, `SNAPCOMPACT_TOOL_CALL_MAX_CHARS`, and `SNAPCOMPACT_TRUNCATE_HEAD_RATIO` for reuse when configuring truncation limits
|
|
26
|
+
- Added provider-specific snapcompact frame-shape presets and shape helpers (`SNAPCOMPACT_SHAPES`, `resolveSnapcompactShape`, `isSnapcompactShape`) so callers can consistently select validated image-frame geometry for archive renders
|
|
27
|
+
- Added `file-operations.md` and `snapcompact-summary.md` prompts to preserve file-read/write context and frame metadata in the compaction prompt flow
|
|
28
|
+
- Added a full `packages/snapcompact/research` experiment and visualization suite for running snapcompact SQuAD studies, provider probes, and activation-style analyses
|
|
29
|
+
- Added package-level TypeScript exports so Prometheus can stage typed access to snapcompact APIs before runtime wiring is enabled
|
|
30
|
+
- Restored `@prometheus-ai/snapcompact` as a private staging package, including bitmap-frame rendering helpers, archive helpers, and the local `snapcompactCompact()` strategy.
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
|
|
34
|
+
- **Changed the per-provider default shapes to the spacing-tuned cells.** The previous shapes were tuned on the SQuAD *prose* eval, where dense cells won; a new tool-result legibility benchmark (`research/toolbench.py` — real `search`/`read`/`find` output with structure-sensitive QA) showed the prose-era density erases the line numbers and indentation that code/search output depends on. Anthropic moves from `6x12-dim` to `11on16-bw` (opus-4.8 f1 .806 vs .755 for plain `8on16-bw` and .351 for `6x12-dim`, which fell below the OCR ~16px/char floor and abstained); OpenAI and Google move to `8on22-bw` (gemini-3.5-flash f1 .934 vs .807 for `8on16-bw` and .287 for `doc-8on16-sent-dim`; same leading win on gpt-5.5/gpt-5.4-mini). Kimi and GLM keep their measured `8on16-bw`. The bigger cells pack fewer chars per frame, so inline frame-swapping now breaks even at a larger tool-result size
|
|
35
|
+
- `serializeConversation` now skips tool call/result pairs whose result is flagged contextually useless (`useless: true`, non-error), so archived frames stop carrying zero-match searches and timed-out waits
|
|
36
|
+
- Frames are no longer padded to a square: the native renderer clips each PNG's height to the text rows actually printed, so a partially filled frame (typically the newest) bills only the pixel rows it uses
|
|
37
|
+
- **Changed the OpenAI default shape from `6x6u-sent` to `8on16-bw`.** A production-regime mono eval (gpt-5.5, the full 800k-char SQuAD flow in one request, n=50) scored the old dense default f1 .602 vs .851 for `8on16-bw` rendered by the production pipeline, at near-equal total cost (the dense cells burned the frame savings on reasoning tokens); chunked exp14 had already scored `8on16-bw` .906. `SHAPES.openaiDense` is renamed to `SHAPES.openai`
|
|
38
|
+
- **Changed the Google default shape from `8x8r-sent` to `doc-8on16-sent-dim`.** Production-rendered mono eval on gemini-3.5-flash (400k chars, one request, n=25): f1 .900 vs .853 for the repeated grid at lower cost, agreeing with the chunked round-2 winner
|
|
39
|
+
- **Changed the Anthropic default shape from `8x8r-bw` to `6x12-dim`.** Production mono eval on claude-fable (400k chars, one request, n=25): f1 .840 vs .877 for the repeated grid — within noise — at 37% lower cost (12 frames instead of 21 per 400k chars), with clean completions in every probe; opus reads the same trade (.800 vs .833 at 42% lower cost)
|
|
40
|
+
- `normalize()` now keeps line structure: whitespace runs containing a line break collapse to `NEWLINE_GLYPH` (U+2588 FULL BLOCK, drawn by the native renderer as a pitch-black cell one character wide) instead of a plain space; leading/trailing breaks are trimmed, and the frame-reading prompt explains the marker
|
|
41
|
+
- `normalize()` now skips characters the fonts cannot render instead of printing `?` blanks: whole ANSI escape sequences are stripped, and bare control characters, zero-width format characters (ZWSP, BOM, directional marks), combining marks, and lone surrogates are dropped without occupying a cell; `?` remains the fallback for unsupported graphic characters only
|
|
42
|
+
- Changed truncation in archived tool output to keep both the beginning and end of long text using a configurable head/tail ratio instead of a single hard cut
|
|
43
|
+
- Changed tool-result text rendering so archived tool results are shown in dim gray ink by default and the summary prompt notes that dim text is archived tool output
|
|
44
|
+
- Changed `RenderedFrame` visible-character accounting so `chars` no longer includes invisible dim-control markers
|
|
45
|
+
- Changed the file-operations summary block to a single `<files>` tag: one grouped, prefix-folded directory tree with per-file `(Read)`/`(Write)`/`(RW)` markers, replacing the separate `<read-files>`/`<modified-files>` lists; `upsertSnapcompactFileOperations` takes the cumulative read set to distinguish `(RW)` from blind writes
|
|
46
|
+
|
|
47
|
+
### Fixed
|
|
48
|
+
|
|
49
|
+
- Fixed frame rendering at archive chunk boundaries to reopen dim spans when a chunk ends inside a dimmed tool-result segment
|
|
50
|
+
- Fixed message serialization to strip user- and assistant-provided dim markers so only renderer-generated dim spans can be applied
|
package/README.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# @prometheus-ai/snapcompact
|
|
2
|
+
|
|
3
|
+
Bitmap-frame context compression for vision-capable LLMs.
|
|
4
|
+
|
|
5
|
+
Instead of asking an LLM to summarize discarded conversation history, snapcompact serializes it and renders the text into dense PNG frames of pixel-font glyphs that vision models read back directly. The whole pass is local and deterministic — no LLM call, no API key, no latency beyond rendering. Rasterization and PNG encoding happen in native code (`@prometheus-ai/natives`).
|
|
6
|
+
|
|
7
|
+
Built for [prometheus](https://github.com/uttamtrivedi/Prometheus)'s compaction pipeline, but the rendering API works on arbitrary text.
|
|
8
|
+
|
|
9
|
+
## How it works
|
|
10
|
+
|
|
11
|
+
1. Discarded history is serialized to compact text (`serializeConversation`), with per-tool-result and per-argument character caps.
|
|
12
|
+
2. Text is normalized for the bundled bitmap fonts (`normalize`): ANSI sequences stripped, whitespace collapsed, newline runs folded into a single full-block glyph so line structure survives.
|
|
13
|
+
3. Pages of text are rasterized into PNG frames (`render` / `renderMany`). Frame width is fixed per shape; height hugs the rows actually printed, so a partially filled frame never bills blank pixel rows.
|
|
14
|
+
4. Frames persist in the compaction entry's `preserveData` and are re-attached to the summary message on every context rebuild.
|
|
15
|
+
|
|
16
|
+
Frame shapes are provider-aware, chosen by SQuAD recall evals (see `research/`) against real provider billing:
|
|
17
|
+
|
|
18
|
+
| Reader | Default shape | Notes |
|
|
19
|
+
| --- | --- | --- |
|
|
20
|
+
| Anthropic | `6x12-dim` | X.org 6x12 glyphs, stopwords dimmed gray; high-res Claude lines get 1932px frames |
|
|
21
|
+
| Google | `doc-8on16-sent-dim` @2048 | Two newspaper columns, sentence-hue ink; Gemini bills a fixed per-image budget, so larger frames are free chars |
|
|
22
|
+
| OpenAI | `8on16-bw` | 8x13 glyphs on a patch-aligned 16px pitch, sent at `detail: "original"` |
|
|
23
|
+
| Unknown | Anthropic shape | Per-provider image-count budgets guard against gateways that silently drop frames |
|
|
24
|
+
|
|
25
|
+
`resolveShape({ api, id })` matches the model id, not just the wire API — a Claude routed through Vertex or OpenRouter keeps its Claude shape, priced for the gateway actually carrying the request.
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
bun add @prometheus-ai/snapcompact
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Ships TypeScript source directly (no build step); requires Bun ≥ 1.3.14.
|
|
34
|
+
|
|
35
|
+
## Usage
|
|
36
|
+
|
|
37
|
+
Render arbitrary text into LLM image blocks:
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import { renderMany, frames, resolveShape } from "@prometheus-ai/snapcompact";
|
|
41
|
+
|
|
42
|
+
const images = renderMany(longText, { model }); // ImageContent[], first page first
|
|
43
|
+
const count = frames(longText, { model }); // frame count without rendering
|
|
44
|
+
const shape = resolveShape(model); // eval-optimal Shape for the reader
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Run a full compaction pass over prepared messages:
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
import { compact } from "@prometheus-ai/snapcompact";
|
|
51
|
+
|
|
52
|
+
const result = await compact(preparation, { model, maxFrames: 8 });
|
|
53
|
+
// result.summary — text summary with <files> operations block
|
|
54
|
+
// result.preserveData — frame archive, re-attachable via getPreservedArchive() + images()
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## API surface
|
|
58
|
+
|
|
59
|
+
- **Compaction**: `compact`, `CompactionPreparation`, `CompactionResult`, `getPreservedArchive`, `images`
|
|
60
|
+
- **Rendering**: `render`, `renderMany`, `frames`, `geometry`
|
|
61
|
+
- **Shapes**: `SHAPES`, `SHAPE_VARIANTS`, `resolveShape`, `idealShapeVariant`, `isShape`, `isShapeVariantName`
|
|
62
|
+
- **Text**: `serializeConversation`, `normalize`, `dimStopwords`, `wrap`
|
|
63
|
+
- **Budgets**: `providerImageBudget`, `providerFrameBudget`, `MAX_FRAMES`, `FRAME_TOKEN_ESTIMATE`
|
|
64
|
+
- **File ops**: `createFileOps`, `computeFileLists`, `upsertFileOperations`
|
|
65
|
+
|
|
66
|
+
## References
|
|
67
|
+
|
|
68
|
+
- [Monorepo README](https://github.com/uttamtrivedi/Prometheus#readme)
|
|
69
|
+
- [Compaction architecture](../../docs/compaction.md)
|
|
70
|
+
- [CHANGELOG](./CHANGELOG.md)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./snapcompact";
|
|
@@ -0,0 +1,523 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Snapcompact compaction: archive conversation history as dense bitmap images.
|
|
3
|
+
*
|
|
4
|
+
* Instead of asking an LLM to summarize discarded history, the serialized
|
|
5
|
+
* conversation is rendered into PNG frames of pixel-font text that vision
|
|
6
|
+
* models read back directly, like an archivist at a snapcompact frame
|
|
7
|
+
* reader. Frames are `frameSize` wide; their height hugs the text rows
|
|
8
|
+
* actually printed, so a partially filled frame never bills blank rows.
|
|
9
|
+
*
|
|
10
|
+
* The frame shape is provider-aware. Original choices came from the SQuAD
|
|
11
|
+
* prose evals (`packages/snapcompact`, 200k-token monolithic runs); the
|
|
12
|
+
* spacing choices below come from the tool-result legibility bench
|
|
13
|
+
* (`research/toolbench.py`, real search/read/find output with structure QA),
|
|
14
|
+
* which exposed that the prose-tuned dense cells erase the line numbers and
|
|
15
|
+
* indentation that code/search output depends on:
|
|
16
|
+
*
|
|
17
|
+
* - **Anthropic** (`11on16-bw`): 8x13 glyphs on an 11px advance (extra
|
|
18
|
+
* letter-spacing), black ink. On the tool-result bench, tracking the
|
|
19
|
+
* readable cell beat plain `8on16-bw` (opus-4.8 f1 .806 vs .755) and far
|
|
20
|
+
* beat the prior dense `6x12-dim` (.351, which fell below the OCR ~16px/char
|
|
21
|
+
* floor and abstained). Opus 4.7+/Fable/Mythos ingest high-res natively
|
|
22
|
+
* (2576px edge, 4,784 visual-token cap), so those lines get 1932px frames:
|
|
23
|
+
* same bill, fewer frames. Older Claude lines downscale past 1568px.
|
|
24
|
+
* - **Google** (`8on22-bw` @2048): 8x13 glyphs on a 22px pitch (extra line
|
|
25
|
+
* spacing), black ink. Leading lifted gemini-3.5-flash to f1 .934 vs .807
|
|
26
|
+
* for `8on16-bw` and .287 for the prior `doc-8on16-sent-dim`. Gemini 3.x
|
|
27
|
+
* bills a fixed `media_resolution` budget per image (default 1,120 tokens)
|
|
28
|
+
* regardless of pixels, so the 2048px frame carries more chars at the same
|
|
29
|
+
* bill.
|
|
30
|
+
* - **OpenAI** (`8on22-bw`): same leading win (gpt-5.5/gpt-5.4-mini). Patch
|
|
31
|
+
* billing (32px × 1.2, 10k-patch budget at `detail: "original"`) is
|
|
32
|
+
* area-proportional, so resolution cannot improve chars/$ — 1568 stays.
|
|
33
|
+
* `detail: "high"` would downgrade (2,500-patch cap); `original` is sent.
|
|
34
|
+
* - **Unknown providers** default to the Anthropic shape. Gateways can
|
|
35
|
+
* defeat any shape silently: OpenRouter enforces a per-model image cap
|
|
36
|
+
* (measured: 8 images for glm-4.6v — frames past the cap are dropped with
|
|
37
|
+
* no error, billed tokens plateau exactly at 8x frame cost). The same
|
|
38
|
+
* frames routed direct to the vendor read fine (glm f1 .20 -> .78), so
|
|
39
|
+
* `providerImageBudget` caps per-request images per provider (OpenRouter
|
|
40
|
+
* 8, unknown 5) and `compact()` keeps any archive overflow as a text tail
|
|
41
|
+
* on the summary instead of rendering frames that would be dropped.
|
|
42
|
+
*
|
|
43
|
+
* The whole pass is local and deterministic — no LLM call, no API key, no
|
|
44
|
+
* latency beyond rendering. Rasterization and PNG encoding happen in native
|
|
45
|
+
* code (`renderSnapcompactPng` in `crates/prometheus-natives/src/snapcompact.rs`).
|
|
46
|
+
* Frames persist in the compaction entry's `preserveData` and are
|
|
47
|
+
* re-attached to the compaction summary message on every context rebuild.
|
|
48
|
+
*/
|
|
49
|
+
import type { Api, ImageContent, Message, Model } from "@prometheus-ai/ai";
|
|
50
|
+
/** One eval-validated frame shape: font, cell, ink, repetition, and size. */
|
|
51
|
+
export interface Shape {
|
|
52
|
+
/** Bundled font in the native renderer. */
|
|
53
|
+
font: "5x8" | "8x8" | "6x12" | "8x13";
|
|
54
|
+
/** Target cell advance in pixels; differing from the font's natural cell
|
|
55
|
+
* renders via Lanczos stretch (anti-aliased RGB frame). */
|
|
56
|
+
cellWidth: number;
|
|
57
|
+
/** Target cell pitch in pixels. */
|
|
58
|
+
cellHeight: number;
|
|
59
|
+
/** `false` → glyphs drawn at natural size on the cell pitch (8on16);
|
|
60
|
+
* `true`/`undefined` → legacy auto Lanczos stretch when cell ≠ natural. */
|
|
61
|
+
stretch?: boolean;
|
|
62
|
+
/** Ink: `sent` cycles six hues at sentence boundaries; `bw` is black. */
|
|
63
|
+
variant: "sent" | "bw";
|
|
64
|
+
/** Print stopwords in dim ink (research `dim`/`sent-dim` variants). */
|
|
65
|
+
stopwordDim?: boolean;
|
|
66
|
+
/** 1/undefined = row-major grid; 2 = two word-wrapped newspaper columns
|
|
67
|
+
* (research `doc`). */
|
|
68
|
+
columns?: number;
|
|
69
|
+
/** Each text line is printed this many times; copies after the first sit
|
|
70
|
+
* on a pale highlight band (redundancy coding). */
|
|
71
|
+
lineRepeat: number;
|
|
72
|
+
/** Frame edge in pixels. */
|
|
73
|
+
frameSize: number;
|
|
74
|
+
/** Per-frame billed-token estimate for the shape's target provider. */
|
|
75
|
+
frameTokenEstimate: number;
|
|
76
|
+
/** Resolution hint attached to frame images (OpenAI-only). */
|
|
77
|
+
imageDetail?: ImageContent["detail"];
|
|
78
|
+
}
|
|
79
|
+
/** Geometry half of a {@link Shape}: everything except provider billing. */
|
|
80
|
+
export type ShapeGeometry = Omit<Shape, "frameTokenEstimate" | "imageDetail">;
|
|
81
|
+
/**
|
|
82
|
+
* Frame variants exercised by the SQuAD evals in `research/` that the native
|
|
83
|
+
* renderer reproduces faithfully, keyed by their research names. Font codes:
|
|
84
|
+
* `8x8u` unscii square cell, `8x8r` unscii with every line printed twice
|
|
85
|
+
* (redundancy coding), `6x6u` unscii Lanczos-squeezed to 6x6 (densest
|
|
86
|
+
* readable cell), `5x8` the X.org legacy font on its 2576px frame, `6x12`
|
|
87
|
+
* and `8x13` the X.org misc fonts, `8on16` 8x13 glyphs on an 8x16 cell pitch
|
|
88
|
+
* (no stretch, extra leading), `8on22` the same glyphs on a 22px pitch (more
|
|
89
|
+
* leading), `11on16` the same glyphs on an 11px advance (more tracking),
|
|
90
|
+
* `doc-` prefixed shapes a two-column word-wrapped newspaper layout. Ink:
|
|
91
|
+
* `sent` cycles six hues at sentence boundaries, `bw` is plain black, `-dim`
|
|
92
|
+
* suffix prints stopwords in gray.
|
|
93
|
+
*/
|
|
94
|
+
export declare const SHAPE_VARIANTS: {
|
|
95
|
+
readonly "8x8r-bw": {
|
|
96
|
+
readonly font: "8x8";
|
|
97
|
+
readonly cellWidth: 8;
|
|
98
|
+
readonly cellHeight: 8;
|
|
99
|
+
readonly variant: "bw";
|
|
100
|
+
readonly lineRepeat: 2;
|
|
101
|
+
readonly frameSize: 1568;
|
|
102
|
+
};
|
|
103
|
+
readonly "8x8r-sent": {
|
|
104
|
+
readonly font: "8x8";
|
|
105
|
+
readonly cellWidth: 8;
|
|
106
|
+
readonly cellHeight: 8;
|
|
107
|
+
readonly variant: "sent";
|
|
108
|
+
readonly lineRepeat: 2;
|
|
109
|
+
readonly frameSize: 1568;
|
|
110
|
+
};
|
|
111
|
+
readonly "8x8u-bw": {
|
|
112
|
+
readonly font: "8x8";
|
|
113
|
+
readonly cellWidth: 8;
|
|
114
|
+
readonly cellHeight: 8;
|
|
115
|
+
readonly variant: "bw";
|
|
116
|
+
readonly lineRepeat: 1;
|
|
117
|
+
readonly frameSize: 1568;
|
|
118
|
+
};
|
|
119
|
+
readonly "8x8u-sent": {
|
|
120
|
+
readonly font: "8x8";
|
|
121
|
+
readonly cellWidth: 8;
|
|
122
|
+
readonly cellHeight: 8;
|
|
123
|
+
readonly variant: "sent";
|
|
124
|
+
readonly lineRepeat: 1;
|
|
125
|
+
readonly frameSize: 1568;
|
|
126
|
+
};
|
|
127
|
+
readonly "6x6u-bw": {
|
|
128
|
+
readonly font: "8x8";
|
|
129
|
+
readonly cellWidth: 6;
|
|
130
|
+
readonly cellHeight: 6;
|
|
131
|
+
readonly variant: "bw";
|
|
132
|
+
readonly lineRepeat: 1;
|
|
133
|
+
readonly frameSize: 1568;
|
|
134
|
+
};
|
|
135
|
+
readonly "6x6u-sent": {
|
|
136
|
+
readonly font: "8x8";
|
|
137
|
+
readonly cellWidth: 6;
|
|
138
|
+
readonly cellHeight: 6;
|
|
139
|
+
readonly variant: "sent";
|
|
140
|
+
readonly lineRepeat: 1;
|
|
141
|
+
readonly frameSize: 1568;
|
|
142
|
+
};
|
|
143
|
+
readonly "5x8-bw": {
|
|
144
|
+
readonly font: "5x8";
|
|
145
|
+
readonly cellWidth: 5;
|
|
146
|
+
readonly cellHeight: 8;
|
|
147
|
+
readonly variant: "bw";
|
|
148
|
+
readonly lineRepeat: 1;
|
|
149
|
+
readonly frameSize: 2576;
|
|
150
|
+
};
|
|
151
|
+
readonly "5x8-sent": {
|
|
152
|
+
readonly font: "5x8";
|
|
153
|
+
readonly cellWidth: 5;
|
|
154
|
+
readonly cellHeight: 8;
|
|
155
|
+
readonly variant: "sent";
|
|
156
|
+
readonly lineRepeat: 1;
|
|
157
|
+
readonly frameSize: 2576;
|
|
158
|
+
};
|
|
159
|
+
readonly "6x12-dim": {
|
|
160
|
+
readonly font: "6x12";
|
|
161
|
+
readonly cellWidth: 6;
|
|
162
|
+
readonly cellHeight: 12;
|
|
163
|
+
readonly variant: "bw";
|
|
164
|
+
readonly stopwordDim: true;
|
|
165
|
+
readonly lineRepeat: 1;
|
|
166
|
+
readonly frameSize: 1568;
|
|
167
|
+
};
|
|
168
|
+
readonly "8x13-bw": {
|
|
169
|
+
readonly font: "8x13";
|
|
170
|
+
readonly cellWidth: 8;
|
|
171
|
+
readonly cellHeight: 13;
|
|
172
|
+
readonly variant: "bw";
|
|
173
|
+
readonly lineRepeat: 1;
|
|
174
|
+
readonly frameSize: 1568;
|
|
175
|
+
};
|
|
176
|
+
readonly "8on16-bw": {
|
|
177
|
+
readonly font: "8x13";
|
|
178
|
+
readonly cellWidth: 8;
|
|
179
|
+
readonly cellHeight: 16;
|
|
180
|
+
readonly stretch: false;
|
|
181
|
+
readonly variant: "bw";
|
|
182
|
+
readonly lineRepeat: 1;
|
|
183
|
+
readonly frameSize: 1568;
|
|
184
|
+
};
|
|
185
|
+
readonly "8on22-bw": {
|
|
186
|
+
readonly font: "8x13";
|
|
187
|
+
readonly cellWidth: 8;
|
|
188
|
+
readonly cellHeight: 22;
|
|
189
|
+
readonly stretch: false;
|
|
190
|
+
readonly variant: "bw";
|
|
191
|
+
readonly lineRepeat: 1;
|
|
192
|
+
readonly frameSize: 1568;
|
|
193
|
+
};
|
|
194
|
+
readonly "11on16-bw": {
|
|
195
|
+
readonly font: "8x13";
|
|
196
|
+
readonly cellWidth: 11;
|
|
197
|
+
readonly cellHeight: 16;
|
|
198
|
+
readonly stretch: false;
|
|
199
|
+
readonly variant: "bw";
|
|
200
|
+
readonly lineRepeat: 1;
|
|
201
|
+
readonly frameSize: 1568;
|
|
202
|
+
};
|
|
203
|
+
readonly "doc-8on16-bw": {
|
|
204
|
+
readonly font: "8x13";
|
|
205
|
+
readonly cellWidth: 8;
|
|
206
|
+
readonly cellHeight: 16;
|
|
207
|
+
readonly stretch: false;
|
|
208
|
+
readonly variant: "bw";
|
|
209
|
+
readonly columns: 2;
|
|
210
|
+
readonly lineRepeat: 1;
|
|
211
|
+
readonly frameSize: 1568;
|
|
212
|
+
};
|
|
213
|
+
readonly "doc-8on16-sent": {
|
|
214
|
+
readonly font: "8x13";
|
|
215
|
+
readonly cellWidth: 8;
|
|
216
|
+
readonly cellHeight: 16;
|
|
217
|
+
readonly stretch: false;
|
|
218
|
+
readonly variant: "sent";
|
|
219
|
+
readonly columns: 2;
|
|
220
|
+
readonly lineRepeat: 1;
|
|
221
|
+
readonly frameSize: 1568;
|
|
222
|
+
};
|
|
223
|
+
readonly "doc-8on16-sent-dim": {
|
|
224
|
+
readonly font: "8x13";
|
|
225
|
+
readonly cellWidth: 8;
|
|
226
|
+
readonly cellHeight: 16;
|
|
227
|
+
readonly stretch: false;
|
|
228
|
+
readonly variant: "sent";
|
|
229
|
+
readonly stopwordDim: true;
|
|
230
|
+
readonly columns: 2;
|
|
231
|
+
readonly lineRepeat: 1;
|
|
232
|
+
readonly frameSize: 1568;
|
|
233
|
+
};
|
|
234
|
+
};
|
|
235
|
+
/** Research name of one renderable frame variant. */
|
|
236
|
+
export type ShapeVariantName = keyof typeof SHAPE_VARIANTS;
|
|
237
|
+
/** All variant names, in declaration order (for settings enums). */
|
|
238
|
+
export declare const SHAPE_VARIANT_NAMES: readonly ShapeVariantName[];
|
|
239
|
+
/** Runtime guard for variant names loaded from config. */
|
|
240
|
+
export declare function isShapeVariantName(value: unknown): value is ShapeVariantName;
|
|
241
|
+
/** Eval-validated shapes, keyed by the provider family they won on. */
|
|
242
|
+
export declare const SHAPES: {
|
|
243
|
+
/** `11on16-bw`: 8x13 glyphs on an 11px advance (extra tracking), black ink.
|
|
244
|
+
* Tool-result legibility bench (real search/read/find output, structure QA)
|
|
245
|
+
* on opus-4.8: f1 .806 vs .755 for plain `8on16-bw` and .351 for the prior
|
|
246
|
+
* `6x12-dim` default — letter-spacing the readable cell wins; the dense
|
|
247
|
+
* 6x12 was below the OCR ~16px/char floor and abstained. */
|
|
248
|
+
anthropic: Shape;
|
|
249
|
+
/** `8on22-bw`: 8x13 glyphs on a 22px pitch (extra leading), black ink.
|
|
250
|
+
* Tool-result legibility bench on gemini-3.5-flash: f1 .934 vs .807 for
|
|
251
|
+
* plain `8on16-bw` and .287 for the prior `doc-8on16-sent-dim`; the
|
|
252
|
+
* line-spacing reduces row crowding so line numbers stay legible. */
|
|
253
|
+
google: Shape;
|
|
254
|
+
/** `8on22-bw`: 8x13 glyphs on a 22px pitch (extra leading), black ink.
|
|
255
|
+
* Same line-spacing win for OpenAI; bench on gpt-5.5/gpt-5.4-mini showed
|
|
256
|
+
* leading lifts recall on the readable cell over plain `8on16-bw`. */
|
|
257
|
+
openai: Shape;
|
|
258
|
+
/** Original 5x8 X.org shape (pre-shape-table sessions rendered this). */
|
|
259
|
+
legacy: Shape;
|
|
260
|
+
};
|
|
261
|
+
/** Runtime guard for shape overrides loaded from config or preserve data. */
|
|
262
|
+
export declare function isShape(value: unknown): value is Shape;
|
|
263
|
+
/** One model line's ideal format: variant plus an optional frame-size
|
|
264
|
+
* override when the line reads larger frames at no extra cost. */
|
|
265
|
+
export interface IdealShape {
|
|
266
|
+
variant: ShapeVariantName;
|
|
267
|
+
frameSize?: number;
|
|
268
|
+
}
|
|
269
|
+
/** Eval-ideal format for a model id, or undefined when unmeasured. */
|
|
270
|
+
export declare function idealShapeVariant(modelId: string): IdealShape | undefined;
|
|
271
|
+
/** What will read the frames: the wire API (billing) and model id (shape). */
|
|
272
|
+
export interface ShapeTarget {
|
|
273
|
+
api?: Api;
|
|
274
|
+
id?: string;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Pick the frame shape for a reader. An explicit `variant` (anything but
|
|
278
|
+
* `"auto"`) forces that geometry; otherwise the model id selects the
|
|
279
|
+
* eval-winning shape — and frame size — for its model line, falling back to
|
|
280
|
+
* the API family's winner when the model is unmeasured. Billing (token
|
|
281
|
+
* estimate, detail hint) always follows the API family actually carrying
|
|
282
|
+
* the request, computed for the resolved frame size. Accepts a full Prometheus AI
|
|
283
|
+
* `Model` or any `{ api, id }` subset.
|
|
284
|
+
*/
|
|
285
|
+
export declare function resolveShape(model?: ShapeTarget, variant?: ShapeVariantName | "auto"): Shape;
|
|
286
|
+
/** Legacy frame edge in pixels (the 5x8 shape's eval-validated size). New
|
|
287
|
+
* shapes carry their own `frameSize`. */
|
|
288
|
+
export declare const FRAME_SIZE = 2576;
|
|
289
|
+
/** Maximum frames carried on a compaction entry. Oldest frames are dropped
|
|
290
|
+
* first once the budget is exceeded (mirrors how iterative text summaries
|
|
291
|
+
* fade the oldest detail). */
|
|
292
|
+
export declare const MAX_FRAMES = 8;
|
|
293
|
+
/** Conservative per-frame token estimate used for context budgeting
|
|
294
|
+
* (upper bound across shapes: Anthropic bills 1568*1568/750 ≈ 3,278). */
|
|
295
|
+
export declare const FRAME_TOKEN_ESTIMATE = 3300;
|
|
296
|
+
/**
|
|
297
|
+
* Per-request image-count budgets by provider id. Routers and smaller
|
|
298
|
+
* providers enforce hard caps and silently DROP images past them (measured:
|
|
299
|
+
* OpenRouter caps at 8 — images 9+ vanish with no error and billed tokens
|
|
300
|
+
* plateau at 8x frame cost). First-party APIs allow far more; their values
|
|
301
|
+
* are conservative policy caps well under the measured hard limits
|
|
302
|
+
* (Anthropic 100, OpenAI 500, Gemini ~2500).
|
|
303
|
+
*/
|
|
304
|
+
export declare const PROVIDER_IMAGE_BUDGETS: Record<string, number>;
|
|
305
|
+
/** Safe floor for unknown providers (strictest mainstream measured: Groq ~5). */
|
|
306
|
+
export declare const DEFAULT_PROVIDER_IMAGE_BUDGET = 5;
|
|
307
|
+
/** Per-request image budget for `provider`; unknown providers get the floor. */
|
|
308
|
+
export declare function providerImageBudget(provider: string | undefined): number;
|
|
309
|
+
/** Archive frame budget for `provider`: its image budget clamped to {@link MAX_FRAMES}. */
|
|
310
|
+
export declare function providerFrameBudget(provider: string | undefined): number;
|
|
311
|
+
/** Key under `CompactionEntry.preserveData` holding the frame archive. */
|
|
312
|
+
export declare const PRESERVE_KEY = "snapcompact";
|
|
313
|
+
/** One developed snapcompact frame: a base64 PNG plus its reading geometry. */
|
|
314
|
+
export interface Frame {
|
|
315
|
+
/** Base64-encoded PNG. */
|
|
316
|
+
data: string;
|
|
317
|
+
mimeType: string;
|
|
318
|
+
/** Characters per row in the frame grid (per-column width on doc frames). */
|
|
319
|
+
cols: number;
|
|
320
|
+
/** Text rows in the frame grid (unique lines, not repeated copies). */
|
|
321
|
+
rows: number;
|
|
322
|
+
/** Characters actually printed onto this frame. */
|
|
323
|
+
chars: number;
|
|
324
|
+
/** Shape metadata (absent on legacy frames, which are 5x8 `sent`). */
|
|
325
|
+
font?: Shape["font"];
|
|
326
|
+
variant?: Shape["variant"];
|
|
327
|
+
lineRepeat?: number;
|
|
328
|
+
/** 2 on two-column doc frames; absent on row-major grid frames. */
|
|
329
|
+
columns?: number;
|
|
330
|
+
/** True when stopwords were printed in dim ink. */
|
|
331
|
+
stopwordDim?: boolean;
|
|
332
|
+
/** Resolution hint forwarded to the provider when re-attaching. */
|
|
333
|
+
detail?: ImageContent["detail"];
|
|
334
|
+
}
|
|
335
|
+
/** Frame archive persisted under `preserveData[PRESERVE_KEY]`. */
|
|
336
|
+
export interface Archive {
|
|
337
|
+
/** Frames ordered oldest to newest. */
|
|
338
|
+
frames: Frame[];
|
|
339
|
+
/** Characters currently readable across all frames. */
|
|
340
|
+
totalChars: number;
|
|
341
|
+
/** Characters dropped so far to respect the frame budget. */
|
|
342
|
+
truncatedChars: number;
|
|
343
|
+
/** Most recent slice of archived history that exceeded the frame budget,
|
|
344
|
+
* kept verbatim as normalized text (dim markers and newline glyphs
|
|
345
|
+
* included). Shipped as plain text in the compaction summary and folded
|
|
346
|
+
* back into frames by the next compaction. */
|
|
347
|
+
textTail?: string;
|
|
348
|
+
}
|
|
349
|
+
export interface Geometry {
|
|
350
|
+
/** Characters per row (per-column line width when `columns === 2`). */
|
|
351
|
+
cols: number;
|
|
352
|
+
rows: number;
|
|
353
|
+
/** Characters that fit one frame (nominal upper bound on doc shapes,
|
|
354
|
+
* where real consumption is wrap-dependent). */
|
|
355
|
+
capacity: number;
|
|
356
|
+
}
|
|
357
|
+
export interface Options<TMessage = Message> extends SerializeOptions {
|
|
358
|
+
/** App-level message transformer (same contract as agent-core's `SummaryOptions.convertToLlm`). */
|
|
359
|
+
convertToLlm?: ConvertToLlm<TMessage>;
|
|
360
|
+
/** Model whose provider API selects the frame shape. */
|
|
361
|
+
model?: Pick<Model, "api">;
|
|
362
|
+
/** Caller-owned reasoning/thinking level metadata; currently preserved for compatibility. */
|
|
363
|
+
thinkingLevel?: unknown;
|
|
364
|
+
/** Explicit shape override; wins over `model`. */
|
|
365
|
+
shape?: Shape;
|
|
366
|
+
/** Frame edge in pixels. Defaults to the shape's `frameSize`. */
|
|
367
|
+
frameSize?: number;
|
|
368
|
+
/** Frame budget. Defaults to {@link MAX_FRAMES}. */
|
|
369
|
+
maxFrames?: number;
|
|
370
|
+
}
|
|
371
|
+
/** Result of rendering one frame. */
|
|
372
|
+
export interface RenderedFrame {
|
|
373
|
+
/** Base64-encoded PNG, as returned by the native renderer. */
|
|
374
|
+
data: string;
|
|
375
|
+
cols: number;
|
|
376
|
+
rows: number;
|
|
377
|
+
/** Characters printed (ink toggles excluded; input may be shorter than capacity). */
|
|
378
|
+
chars: number;
|
|
379
|
+
}
|
|
380
|
+
export interface FileOperations {
|
|
381
|
+
read: Set<string>;
|
|
382
|
+
written: Set<string>;
|
|
383
|
+
edited: Set<string>;
|
|
384
|
+
}
|
|
385
|
+
export interface CompactionDetails {
|
|
386
|
+
readFiles: string[];
|
|
387
|
+
modifiedFiles: string[];
|
|
388
|
+
}
|
|
389
|
+
export interface CompactionPreparation<TMessage = Message> {
|
|
390
|
+
/** UUID of first entry to keep. */
|
|
391
|
+
firstKeptEntryId: string;
|
|
392
|
+
/** Messages that will be archived and discarded. */
|
|
393
|
+
messagesToSummarize: TMessage[];
|
|
394
|
+
/** Messages that will be archived as the split-turn prefix, if any. */
|
|
395
|
+
turnPrefixMessages: TMessage[];
|
|
396
|
+
tokensBefore: number;
|
|
397
|
+
/** Summary from previous compaction, for continuity when no prior snapcompact archive exists. */
|
|
398
|
+
previousSummary?: string;
|
|
399
|
+
/** Preserved opaque compaction payload from the previous compaction, if any. */
|
|
400
|
+
previousPreserveData?: Record<string, unknown>;
|
|
401
|
+
/** File operations extracted by the host agent. */
|
|
402
|
+
fileOps: FileOperations;
|
|
403
|
+
}
|
|
404
|
+
export interface CompactionResult<T = CompactionDetails> {
|
|
405
|
+
summary: string;
|
|
406
|
+
shortSummary?: string;
|
|
407
|
+
firstKeptEntryId: string;
|
|
408
|
+
tokensBefore: number;
|
|
409
|
+
details?: T;
|
|
410
|
+
preserveData?: Record<string, unknown>;
|
|
411
|
+
}
|
|
412
|
+
export type ConvertToLlm<TMessage = Message> = (messages: TMessage[]) => Message[];
|
|
413
|
+
export declare function createFileOps(): FileOperations;
|
|
414
|
+
export declare function computeFileLists(fileOps: FileOperations): CompactionDetails;
|
|
415
|
+
export declare function upsertFileOperations(summary: string, readFiles: string[], modifiedFiles: string[], readSet?: ReadonlySet<string>): string;
|
|
416
|
+
/** Default per-tool-result character cap in serialized history. */
|
|
417
|
+
export declare const TOOL_RESULT_MAX_CHARS = 2000;
|
|
418
|
+
/** Default per-argument-value character cap inside serialized tool calls
|
|
419
|
+
* (write/edit bodies otherwise dump whole files into the archive). */
|
|
420
|
+
export declare const TOOL_ARG_MAX_CHARS = 500;
|
|
421
|
+
/** Default character cap across one tool call's full serialized argument list. */
|
|
422
|
+
export declare const TOOL_CALL_MAX_CHARS = 2000;
|
|
423
|
+
/** Default fraction of a truncation budget spent on the head; the remainder
|
|
424
|
+
* keeps the tail, where command errors and test failures usually land. */
|
|
425
|
+
export declare const TRUNCATE_HEAD_RATIO = 0.6;
|
|
426
|
+
/** Zero-width ink toggles understood by the native renderer (shift-out/in):
|
|
427
|
+
* text between them prints in dim gray ink without occupying a cell. */
|
|
428
|
+
export declare const DIM_ON = "\u000E";
|
|
429
|
+
export declare const DIM_OFF = "\u000F";
|
|
430
|
+
/** Character budgets applied while serializing discarded history for frame
|
|
431
|
+
* rendering. Pass `Infinity` to disable an individual cap. */
|
|
432
|
+
export interface SerializeOptions {
|
|
433
|
+
/** Per-tool-result cap. Defaults to {@link TOOL_RESULT_MAX_CHARS}. */
|
|
434
|
+
toolResultMaxChars?: number;
|
|
435
|
+
/** Per-argument-value cap. Defaults to {@link TOOL_ARG_MAX_CHARS}. */
|
|
436
|
+
toolArgMaxChars?: number;
|
|
437
|
+
/** Whole-argument-list cap per call. Defaults to {@link TOOL_CALL_MAX_CHARS}. */
|
|
438
|
+
toolCallMaxChars?: number;
|
|
439
|
+
/** Head share of each budget, clamped to [0, 1]. Defaults to {@link TRUNCATE_HEAD_RATIO}. */
|
|
440
|
+
truncateHeadRatio?: number;
|
|
441
|
+
/** Print tool-result text in dim gray ink so archived conversation reads
|
|
442
|
+
* louder than archived tool noise. Defaults to `true`. */
|
|
443
|
+
dimToolResults?: boolean;
|
|
444
|
+
}
|
|
445
|
+
export declare function serializeConversation(messages: Message[], options?: SerializeOptions): string;
|
|
446
|
+
/** Printed in place of newline runs: the native renderer fills this cell
|
|
447
|
+
* entirely with pitch-black ink, so line structure survives whitespace
|
|
448
|
+
* collapsing at a one-cell cost. */
|
|
449
|
+
export declare const NEWLINE_GLYPH = "\u2588";
|
|
450
|
+
/**
|
|
451
|
+
* Prepare text for printing: strip ANSI escape sequences, collapse horizontal
|
|
452
|
+
* whitespace runs to single spaces and newline-bearing runs to one
|
|
453
|
+
* {@link NEWLINE_GLYPH} (drawn as a pitch-black cell), then fold everything
|
|
454
|
+
* outside the fonts' ASCII + Latin-1 coverage to ASCII approximations.
|
|
455
|
+
* Unrenderable control/format/combining characters are dropped without
|
|
456
|
+
* occupying a cell; `?` remains the fallback for unsupported graphic
|
|
457
|
+
* characters. The zero-width ink toggles {@link DIM_ON}/{@link DIM_OFF} pass
|
|
458
|
+
* through untouched.
|
|
459
|
+
*/
|
|
460
|
+
export declare function normalize(text: string): string;
|
|
461
|
+
/**
|
|
462
|
+
* Wrap each maximal alphabetic run that is a stopword in {@link DIM_ON} /
|
|
463
|
+
* {@link DIM_OFF} so it prints in dim gray ink. Spans that are already dim
|
|
464
|
+
* (e.g. archived tool output) pass through untouched — wrapping there would
|
|
465
|
+
* terminate the enclosing dim span early. Markers are zero-width, so the
|
|
466
|
+
* visible glyph count is unchanged.
|
|
467
|
+
*/
|
|
468
|
+
export declare function dimStopwords(text: string): string;
|
|
469
|
+
/**
|
|
470
|
+
* Greedy word-wrap, no mid-word breaks (hard split only for width+ words) —
|
|
471
|
+
* ported verbatim from `research/exp14_bestgpt.py` `wrap()`. Zero-width dim
|
|
472
|
+
* markers count toward word length here; serialized history places them at
|
|
473
|
+
* word boundaries, so the drift is at most one cell per affected line.
|
|
474
|
+
*/
|
|
475
|
+
export declare function wrap(text: string, width: number): string[];
|
|
476
|
+
export declare function geometry(shape: Shape, size?: number): Geometry;
|
|
477
|
+
/** Render one snapcompact frame from already-normalized text. Doc shapes
|
|
478
|
+
* (`columns === 2`) expect one page of `\n`-joined pre-wrapped lines. */
|
|
479
|
+
export declare function render(text: string, shape: Shape, size?: number): RenderedFrame;
|
|
480
|
+
/** Options for {@link renderMany} and {@link frames}. */
|
|
481
|
+
export interface RenderManyOptions {
|
|
482
|
+
/** Explicit shape; wins over `model`. */
|
|
483
|
+
shape?: Shape;
|
|
484
|
+
/** Model whose `api` selects the eval-optimal shape. */
|
|
485
|
+
model?: Pick<Model, "api">;
|
|
486
|
+
/** Frame edge in px; defaults to the shape's `frameSize`. */
|
|
487
|
+
frameSize?: number;
|
|
488
|
+
/** Hard cap on frames produced; omit for unbounded (caller decides usage). */
|
|
489
|
+
maxFrames?: number;
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Render arbitrary text into snapcompact PNG frames as LLM image blocks
|
|
493
|
+
* (first page first). Synchronous: safe to call from per-request transforms.
|
|
494
|
+
* Empty/whitespace-only input yields no frames.
|
|
495
|
+
*/
|
|
496
|
+
export declare function renderMany(text: string, options?: RenderManyOptions): ImageContent[];
|
|
497
|
+
/** Frames needed to hold `text` at the given shape/size, without rendering.
|
|
498
|
+
* For doc shapes this wraps the text once and counts pages of `2 * rows`
|
|
499
|
+
* lines; for grid shapes it divides by the frame capacity. */
|
|
500
|
+
export declare function frames(text: string, options?: Pick<RenderManyOptions, "shape" | "model" | "frameSize">): number;
|
|
501
|
+
/** Validate and extract a persisted frame archive from `preserveData`. */
|
|
502
|
+
export declare function getPreservedArchive(preserveData: Record<string, unknown> | undefined): Archive | undefined;
|
|
503
|
+
/** Convert archive frames into LLM image blocks (oldest first). */
|
|
504
|
+
export declare function images(archive: Archive): ImageContent[];
|
|
505
|
+
/**
|
|
506
|
+
* Run a snapcompact compaction over prepared messages. Fully local: serializes
|
|
507
|
+
* the discarded history, prints it onto PNG frames in the provider-optimal
|
|
508
|
+
* shape, merges previously archived frames (oldest dropped beyond the
|
|
509
|
+
* budget), and produces a deterministic summary explaining how to read the
|
|
510
|
+
* frames. Pages past the frame budget are never rendered (providers with
|
|
511
|
+
* hard image caps silently drop excess frames on the wire) — the newest
|
|
512
|
+
* unrendered slice survives verbatim as a text tail on the summary and is
|
|
513
|
+
* folded back into frames by the next compaction.
|
|
514
|
+
*
|
|
515
|
+
* Frames archived under a different shape (provider switches, legacy 5x8
|
|
516
|
+
* sessions) are kept as-is — each frame carries its own geometry, and the
|
|
517
|
+
* summary describes the newest shape while noting that older frames may
|
|
518
|
+
* differ.
|
|
519
|
+
*
|
|
520
|
+
* If the previous compaction was text-based, its summary is printed at the
|
|
521
|
+
* head of the frame archive as `[Summary of earlier history]` so no continuity is lost.
|
|
522
|
+
*/
|
|
523
|
+
export declare function compact<TMessage = Message>(preparation: CompactionPreparation<TMessage>, options?: Options<TMessage>): Promise<CompactionResult>;
|