tokenfold 0.3.4 → 0.4.1

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/README.md CHANGED
@@ -15,5 +15,24 @@ const { payload, report } = await compress(input, {
15
15
  });
16
16
  ```
17
17
 
18
+ Opt-in lossy array pruning drops whole array items to hit a token budget instead
19
+ of only restructuring them, replacing each with a `$tf_ref` marker that resolves
20
+ through the local retrieval store. Generic JSON only:
21
+
22
+ ```ts
23
+ import { compress, retrieve } from "tokenfold";
24
+
25
+ const { payload, report } = await compress(feed, {
26
+ format: "json",
27
+ lossy: "heuristic",
28
+ lossyRatio: 0.35, // selection hint, not an enforced budget
29
+ lossyPreserve: ["meta"], // arrays that are never pruned
30
+ });
31
+ const original = await retrieve(hash); // a dropped item's original bytes
32
+ ```
33
+
34
+ Pass the same options to `inspect` to preview the projected savings without
35
+ writing anything to the store.
36
+
18
37
  Requires Node.js 22 or newer. The matching native CLI is installed through an
19
38
  optional platform package; set `TOKENFOLD_BINARY_PATH` to use a custom binary.
package/dist/index.d.ts CHANGED
@@ -5,6 +5,8 @@ export { binaryPath, run, TokenFoldProcessError };
5
5
  export type { Input, ProcessResult, RunOptions };
6
6
  export type CompressionMode = "conservative" | "balanced" | "aggressive";
7
7
  export type TaskScope = "all" | "general" | "code_review" | "change_summary" | "debugging" | "generation" | "api_overview" | "retrieval_qa" | "agent_history";
8
+ /** Selection backend for opt-in lossy pruning. `heuristic` is the only Phase 1 path. */
9
+ export type LossyPath = "heuristic";
8
10
  export interface CompressionOptions {
9
11
  format?: "auto" | "openai" | "anthropic" | "json" | "text" | "command" | "diff";
10
12
  mode?: CompressionMode;
@@ -14,6 +16,22 @@ export interface CompressionOptions {
14
16
  experimental?: boolean;
15
17
  storeOriginals?: boolean;
16
18
  retrieveNamespace?: string;
19
+ /**
20
+ * Opt-in LOSSY JSON array-item pruning (`--lossy`): drops whole array items to hit a token
21
+ * budget instead of only restructuring them. Generic JSON only — on any other format the run
22
+ * is a no-op and `json_prune` comes back as `status: "skipped"` with
23
+ * `skipped_reason: "not_applicable_to_format"`, persisting nothing. Dropped items are replaced
24
+ * by `$tf_ref` markers and stay recoverable via {@link retrieve}; pruning needs a durable
25
+ * filesystem retrieval store, so pass `configPath` when you want one other than the default.
26
+ */
27
+ lossy?: LossyPath;
28
+ /**
29
+ * `--lossy-ratio`: a best-effort selection hint (0.0..=1.0), not an enforced budget — the
30
+ * achieved ratio differs by design. Use `targetTokens` for a real ceiling.
31
+ */
32
+ lossyRatio?: number;
33
+ /** `--lossy-preserve`: dot-separated paths (e.g. `data.results`) whose arrays are never pruned. */
34
+ lossyPreserve?: readonly string[];
17
35
  configPath?: string;
18
36
  signal?: AbortSignal;
19
37
  }
@@ -128,3 +146,21 @@ export interface CompressionResult {
128
146
  }
129
147
  export declare function compress(input: Input, options?: CompressionOptions): Promise<CompressionResult>;
130
148
  export declare function inspect(input: Input, options?: CompressionOptions): Promise<CompressionResult>;
149
+ export interface RetrieveOptions {
150
+ /** `--retrieve-namespace`: the namespace the item was stored under. */
151
+ namespace?: string;
152
+ configPath?: string;
153
+ signal?: AbortSignal;
154
+ }
155
+ /**
156
+ * Restores the original bytes of something a lossy run dropped, or a `storeOriginals` run saved,
157
+ * mirroring `tokenfold retrieve`. `reference` is either a raw hex SHA-256 hash — a compressed
158
+ * payload's `$tf_ref.hash` — or a `[tokenfold:retrieve hash=... namespace=...]` text marker,
159
+ * whose embedded namespace is used when `options.namespace` is omitted. A CompressionReport
160
+ * path is NOT a valid reference: the current report schema carries no per-entry hash, and the
161
+ * CLI rejects it rather than guessing.
162
+ *
163
+ * Throws `TokenFoldProcessError` (`code: "tokenfold_exit"`) when the hash is unknown, its TTL
164
+ * has elapsed, or the reference is malformed — the CLI reports all of those as a non-zero exit.
165
+ */
166
+ export declare function retrieve(reference: string, options?: RetrieveOptions): Promise<Uint8Array>;
package/dist/index.js CHANGED
@@ -3,24 +3,43 @@ import { TokenFoldProcessError } from "./errors.js";
3
3
  import { run } from "./process.js";
4
4
  export { binaryPath, run, TokenFoldProcessError };
5
5
  function argumentsFor(command, options) {
6
- const args = [command, "--json"];
6
+ // The `inspect` subcommand has no --lossy flags of its own; the CLI's lossy preview is
7
+ // `compress --dry-run`, which routes to the same code path and, exactly like `inspect --json`,
8
+ // writes the report to stdout and no payload. So a lossy inspect() becomes that instead.
9
+ const wantsLossy = options.lossy !== undefined ||
10
+ options.lossyRatio !== undefined ||
11
+ (options.lossyPreserve?.length ?? 0) > 0;
12
+ const previewLossy = command === "inspect" && wantsLossy;
13
+ const args = previewLossy ? ["compress", "--json", "--dry-run"] : [command, "--json"];
14
+ const compressing = command === "compress" || previewLossy;
7
15
  if (options.format)
8
16
  args.push("--format", options.format);
9
17
  if (options.mode)
10
18
  args.push("--mode", options.mode);
11
19
  if (options.targetTokens !== undefined)
12
20
  args.push("--target-tokens", String(options.targetTokens));
13
- if (options.disable?.length && command === "compress")
21
+ if (options.disable?.length && compressing)
14
22
  args.push("--disable", options.disable.join(","));
15
23
  if (options.taskScope)
16
24
  args.push("--task-scope", options.taskScope);
17
25
  if (options.experimental)
18
26
  args.push("--experimental");
19
- if (options.storeOriginals && command === "compress")
27
+ if (options.storeOriginals && compressing)
20
28
  args.push("--store-originals");
21
- if (options.retrieveNamespace && command === "compress") {
29
+ if (options.retrieveNamespace && compressing) {
22
30
  args.push("--retrieve-namespace", options.retrieveNamespace);
23
31
  }
32
+ if (wantsLossy && compressing) {
33
+ // `--lossy-ratio`/`--lossy-preserve` are `requires = "lossy"` in the CLI. Forward them even
34
+ // when `lossy` is unset rather than dropping them: silently ignoring a pruning-aggression
35
+ // knob is worse than the CLI's own "the following required arguments were not provided".
36
+ if (options.lossy)
37
+ args.push("--lossy", options.lossy);
38
+ if (options.lossyRatio !== undefined)
39
+ args.push("--lossy-ratio", String(options.lossyRatio));
40
+ for (const path of options.lossyPreserve ?? [])
41
+ args.push("--lossy-preserve", path);
42
+ }
24
43
  if (options.configPath)
25
44
  args.push("--config", options.configPath);
26
45
  return args;
@@ -39,6 +58,16 @@ function parseReport(bytes, result) {
39
58
  });
40
59
  }
41
60
  }
61
+ function throwIfFailed(result) {
62
+ if (result.exitCode === 0)
63
+ return;
64
+ throw new TokenFoldProcessError(`tokenfold exited with status ${result.exitCode ?? result.signal}`, {
65
+ code: "tokenfold_exit",
66
+ exitCode: result.exitCode,
67
+ signal: result.signal,
68
+ stderr: result.stderr,
69
+ });
70
+ }
42
71
  async function execute(command, input, options) {
43
72
  const runOptions = {
44
73
  stdin: input,
@@ -47,14 +76,7 @@ async function execute(command, input, options) {
47
76
  if (options.signal)
48
77
  runOptions.signal = options.signal;
49
78
  const result = await run(argumentsFor(command, options), runOptions);
50
- if (result.exitCode !== 0) {
51
- throw new TokenFoldProcessError(`tokenfold exited with status ${result.exitCode ?? result.signal}`, {
52
- code: "tokenfold_exit",
53
- exitCode: result.exitCode,
54
- signal: result.signal,
55
- stderr: result.stderr,
56
- });
57
- }
79
+ throwIfFailed(result);
58
80
  const reportBytes = command === "compress" ? result.stderr : result.stdout;
59
81
  return {
60
82
  payload: command === "compress" ? result.stdout : Uint8Array.from(Buffer.from(input)),
@@ -67,3 +89,27 @@ export function compress(input, options = {}) {
67
89
  export function inspect(input, options = {}) {
68
90
  return execute("inspect", input, options);
69
91
  }
92
+ /**
93
+ * Restores the original bytes of something a lossy run dropped, or a `storeOriginals` run saved,
94
+ * mirroring `tokenfold retrieve`. `reference` is either a raw hex SHA-256 hash — a compressed
95
+ * payload's `$tf_ref.hash` — or a `[tokenfold:retrieve hash=... namespace=...]` text marker,
96
+ * whose embedded namespace is used when `options.namespace` is omitted. A CompressionReport
97
+ * path is NOT a valid reference: the current report schema carries no per-entry hash, and the
98
+ * CLI rejects it rather than guessing.
99
+ *
100
+ * Throws `TokenFoldProcessError` (`code: "tokenfold_exit"`) when the hash is unknown, its TTL
101
+ * has elapsed, or the reference is malformed — the CLI reports all of those as a non-zero exit.
102
+ */
103
+ export async function retrieve(reference, options = {}) {
104
+ const args = ["retrieve", reference];
105
+ if (options.namespace)
106
+ args.push("--retrieve-namespace", options.namespace);
107
+ if (options.configPath)
108
+ args.push("--config", options.configPath);
109
+ const runOptions = { env: { TOKENFOLD_ANALYTICS_ENABLED: "false" } };
110
+ if (options.signal)
111
+ runOptions.signal = options.signal;
112
+ const result = await run(args, runOptions);
113
+ throwIfFailed(result);
114
+ return result.stdout;
115
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tokenfold",
3
- "version": "0.3.4",
3
+ "version": "0.4.1",
4
4
  "description": "Token-aware compression for LLM payloads, backed by the tokenfold Rust CLI.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -49,11 +49,11 @@
49
49
  "prepack": "npm run build"
50
50
  },
51
51
  "optionalDependencies": {
52
- "@tokenfold/cli-darwin-x64": "0.3.4",
53
- "@tokenfold/cli-darwin-arm64": "0.3.4",
54
- "@tokenfold/cli-linux-x64": "0.3.4",
55
- "@tokenfold/cli-linux-arm64": "0.3.4",
56
- "@tokenfold/cli-win32-x64": "0.3.4"
52
+ "@tokenfold/cli-darwin-x64": "0.4.1",
53
+ "@tokenfold/cli-darwin-arm64": "0.4.1",
54
+ "@tokenfold/cli-linux-x64": "0.4.1",
55
+ "@tokenfold/cli-linux-arm64": "0.4.1",
56
+ "@tokenfold/cli-win32-x64": "0.4.1"
57
57
  },
58
58
  "devDependencies": {
59
59
  "@types/node": "^24.0.0",