ax-tree-compress
v0.1.1
Published
Compress raw CDP AXNode arrays into token-efficient text for LLM consumption
Maintainers
Readme
ax-tree-compress
Compress raw CDP AXNode arrays (e.g. from Accessibility.getFullAXTree or chrome.debugger in a Chrome Extension) into a token-efficient text format for LLM consumption, with sequential @eN references mapped to CDP backendNodeId for downstream actions.
Install
npm install ax-tree-compressUsage
import { formatAXTree } from 'ax-tree-compress'
// nodes from CDP Accessibility.getFullAXTree
const { text, refs, tokenEstimate } = formatAXTree(nodes)With options:
const { text, refs, tokenEstimate } = formatAXTree(nodes, {
interactiveOnly: true, // only buttons, links, inputs, etc.
maxDepth: 5, // limit tree depth
collapseText: true, // merge consecutive text nodes
maxTokens: 2000, // truncate at token budget
})Output Format
@e1 RootWebArea "Hacker News"
@e2 navigation "Main"
@e3 link "Home"
@e4 link "About" [selected]
@e5 main
@e6 heading "Welcome"
@e7 button "Sign Up" [focused]Each @eN reference maps to a CDP backendNodeId in the returned refs object, enabling click/type actions via CDP:
// result.refs = { "@e1": 100, "@e2": 101, "@e3": 102, ... }
const backendNodeId = result.refs["@e7"]Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| interactiveOnly | boolean | false | Only keep interactive elements and their ancestors |
| maxDepth | number | Infinity | Max tree depth |
| skipRoles | string[] | [] | Additional roles to skip (on top of built-in list, see below) |
| rootNodeId | string | first node | Start traversal from a specific subtree |
| maxTokens | number | Infinity | Truncate output at estimated token limit (chars/4) |
| collapseText | boolean | false | Merge consecutive text nodes into one line |
Return Value
interface FormatResult {
text: string // Compressed tree text
refs: Record<string, number> // @eN -> CDP backendNodeId
tokenEstimate: number // Estimated token count (chars/4)
}Benchmark
Tested against real pages (run npx tsx bench/run.ts):
| Page | Raw JSON | Default | interactiveOnly | Compression | |------|----------|---------|-----------------|-------------| | GitHub Explore | 155K tokens | 4.8K tokens | 3.7K tokens | 97–98% reduction | | Hacker News | 155K tokens | 3.2K tokens | 3.0K tokens | 98% reduction | | Wikipedia | 621K tokens | 11.3K tokens | 10.2K tokens | 98% reduction |
--- github-explore (1493 nodes, raw JSON: 607.3 KB, ~155,466 tokens) ---
Preset Size Tokens Ratio Refs Lines
------------------------------------------------------------------
Default 18.7 KB 4,791 32.5x 485 485
collapseText 46.9 KB 12,007 12.9x 779 1133
interactiveOnly 14.6 KB 3,729 41.7x 384 384
interactive + collapse 14.8 KB 3,790 41.0x 384 387
maxDepth=3 12.0 KB 3,066 50.7x 272 272
all optimizations 13.3 KB 3,412 45.6x 341 344
--- hacker-news (1603 nodes, raw JSON: 606.9 KB, ~155,370 tokens) ---
Preset Size Tokens Ratio Refs Lines
------------------------------------------------------------------
Default 12.5 KB 3,189 48.7x 350 350
collapseText 46.8 KB 11,987 13.0x 823 1302
interactiveOnly 11.9 KB 3,034 51.2x 320 320
interactive + collapse 11.9 KB 3,034 51.2x 320 320
maxDepth=3 12.5 KB 3,189 48.7x 350 350
all optimizations 11.9 KB 3,034 51.2x 320 320
--- wikipedia (6360 nodes, raw JSON: 2427.3 KB, ~621,377 tokens) ---
Preset Size Tokens Ratio Refs Lines
------------------------------------------------------------------
Default 44.1 KB 11,287 55.1x 1073 1073
collapseText 203.0 KB 51,966 12.0x 2245 3992
interactiveOnly 39.9 KB 10,218 60.8x 977 977
interactive + collapse 81.0 KB 20,741 30.0x 977 1330
maxDepth=3 21.2 KB 5,418 114.7x 636 636
all optimizations 80.8 KB 20,689 30.0x 974 1327How It Works
- Walk the CDP AXNode tree depth-first
- Skip noise nodes (see built-in skip list below, plus ignored and nameless leaves)
- Assign sequential
@eNrefs to each meaningful node - Annotate interactive states (
focused,selected,checked,expanded/collapsed) - Apply optional filters: interactive-only, depth limit, text collapsing, token truncation
Built-in Skip Roles
These roles are filtered by default (structural/presentational noise):
none, generic, InlineTextBox, LineBreak, LayoutTable, LayoutTableRow, LayoutTableCell, row, rowgroup, columnheader, ListMarker, superscript, subscript, Abbr, Figcaption, StaticText
Use skipRoles option to add more. StaticText is automatically un-skipped when collapseText: true.
