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

ImportExports
jev-treecreateJevTree, parseShape, fromList, flattenLeaves, asRoot, isLeaf, countLeaves, partition, redactCommonSecrets, constants, types
npx jev-treeCLI (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

  1. parseShape(shape) unless validate === false. Ids must be unique. __root is reserved.
  2. A forest (TreeNode[]) becomes a virtual root __root (omitted from path).
  3. Serialize state. Redact. Oversized input, redact throws, or JSON failure → unavailable.
  4. While the current node has children and depth/call budgets remain:
    • one child: take it (reason: 'single'), no model call
    • otherwise choose(children)
  5. choose never sends more than maxFanout criteria. Keys are o0, o1, … so your ids need not be legal Jev choice keys.
  6. Sibling lists larger than maxFanout are partitioned into contiguous buckets. Bucket text is first to last (n options) [id .. id]. Jev picks a bucket, then the walk continues inside it.
  7. Timeouts abort that attempt and do not retry. 429 / overloaded / highest-probability retry up to 4 times with a fresh abort controller.
  8. 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.