jev-tree API (0.1.0)
September 18, 2026 · View on GitHub
Source of truth: src/index.ts and src/cli.ts in
https://github.com/reachjalil/jev-tree. If this file and the code disagree, the code wins.
Runtime: Node.js 22+, ESM. Dependency: ai@7.0.105. Model: typesafe-ai/jev through Vercel AI Gateway (AI_GATEWAY_API_KEY). Choice questions cannot list more than 255 criteria.
Package layout
| Import | Exports |
|---|---|
jev-tree | createJevTree, parseShape, fromList, flattenLeaves, asRoot, isLeaf, countLeaves, partition, redactCommonSecrets, constants, types |
npx jev-tree | CLI (dist/cli.js) |
Nothing else is exported. There is no website runtime, no host for Jev, and no way to send more than maxFanout criteria in one call.
createJevTree(options?)
interface JevTreeOptions {
maxFanout?: number; // default 32; integer 2–255
timeoutMs?: number; // default 8000; > 0
maxDepth?: number; // default 16; 1–64
maxCalls?: number; // default 64; 1–1000
maxInputChars?: number; // default 8000
instructions?: string; // prepended to every choice
evaluator?: TreeEvaluator;
redact?: (text: string) => string;
validate?: boolean; // default true; parseShape before walking
}
Out-of-range options throw RangeError at construction. Returns { select(input): Promise<SelectResult>, stats(): JevTreeStats }.
interface SelectInput {
state: unknown; // string or JSON; treated as untrusted data
shape: TreeNode | TreeNode[];
question?: string; // extra instruction on every choice
}
interface SelectResult {
path: { id: string; label: string }[];
ids: string[];
node: TreeNode; // leaf on success; last node on unavailable
value: unknown;
probability: number | null;
calls: number;
inputTokens: number;
steps: SelectStep[];
reason: 'model' | 'unavailable';
}
Walk
parseShape(shape)unlessvalidate === false. Ids must be unique.__rootis reserved.- A forest (
TreeNode[]) becomes a virtual root__root(omitted frompath). - Serialize
state. Redact. Oversized input, redact throws, or JSON failure →unavailable. - While the current node has children and depth/call budgets remain:
- one child: take it (
reason: 'single'), no model call - otherwise
choose(children)
- one child: take it (
choosenever sends more thanmaxFanoutcriteria. Keys areo0,o1, … so your ids need not be legal Jev choice keys.- Sibling lists larger than
maxFanoutare partitioned into contiguous buckets. Bucket text isfirst to last (n options) [id .. id]. Jev picks a bucket, then the walk continues inside it. - Timeouts abort that attempt and do not retry. 429 / overloaded /
highest-probabilityretry up to 4 times with a fresh abort controller. - Invalid choice keys, invalid probabilities, depth/call exhaustion, or evaluator throws →
reason: 'unavailable'. No leaf is invented.
Shape
interface TreeNode {
id: string; // ≤128 chars, unique, not "__root"
label: string; // ≤200 chars, shown to Jev
description?: string; // ≤500 chars
children?: TreeNode[];
value?: unknown; // returned when this leaf is selected
}
A node with no children (or []) is a leaf. parseShape also accepts { shape: … } JSON envelopes (a wrapper object that has shape and no id).
fromList(items) wraps a flat catalog as a forest of leaves. flattenLeaves(shape) is depth-first, document order.
CLI
npx jev-tree # offline demo
npx jev-tree --live --shape t.json --state "the record"
npx jev-tree --count --shape t.json
--live needs AI_GATEWAY_API_KEY. Timeouts exit 2. Demo answers are fixed; they are not Jev inference.