toolgz 0.2.5 → 0.2.6

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
@@ -2,7 +2,7 @@
2
2
 
3
3
  <p><strong>Your agent spends 30–70k tokens of context on tool definitions before the user types a word. toolgz gets up to ~85% of it back.</strong></p>
4
4
 
5
- <p><em>On a large tool set, at level 3. The safe default (level 1) reclaims 13–32% and gives up nothing — <code>recommendLevel()</code> picks for you.</em></p>
5
+ <p><em>On a large tool set, at level 3. The safe default (level 1) reclaims 13–39% and gives up nothing — <code>recommendLevel()</code> picks for you.</em></p>
6
6
 
7
7
  <p>
8
8
  <a href="#measured-results">420-run cross-provider sweep</a> ·
@@ -39,7 +39,7 @@ const { tools, system } = forAnthropic(c); // send these instead
39
39
  ```
40
40
 
41
41
  `compress(myTools)` with no `level` gives you **level 1** — safe, native tool calling,
42
- provider schema enforcement intact, and 13–32% smaller. The 71–85% figures below are
42
+ provider schema enforcement intact, and 13–39% smaller (13–32% on the synthetic benchmark, 39% on the real 149-tool corpus). The 71–85% figures below are
43
43
  **level 3**, which is what `recommendLevel` returns once a tool set is big enough to
44
44
  amortise the dispatcher. Ask for it explicitly with `{ level: 3 }` if you prefer.
45
45
 
@@ -84,7 +84,7 @@ claim about real deployments.
84
84
  </picture>
85
85
 
86
86
  **All figures below are level 3** (`minified-plus`, the shipped default map style).
87
- Level 1 on the same sweep saves 13–32%; level 2 is dominated by level 3.
87
+ Level 1 on the same sweep saves 13–32%; on the real 149-tool corpus it saves 39%; level 2 is dominated by level 3.
88
88
 
89
89
  | Provider | Model | Tool block | Prompt tokens | Latency | Tasks |
90
90
  |---|---|---:|---:|---:|:-:|
@@ -955,7 +955,7 @@ and it does not get deleted.
955
955
  ## Development
956
956
 
957
957
  ```bash
958
- npm test # 268 tests, offline, no cost
958
+ npm test # 272 tests, offline, no cost
959
959
  npm run build # tsc → dist/ with .d.ts
960
960
 
961
961
  npx tsx bench/harness/run-multi.ts --provider=all --reps=3 --variants # costs money
package/dist/recommend.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { defaultNamespaceOf } from "./render/index.js";
2
+ import { compress } from "./index.js";
2
3
  /**
3
4
  * Pick a level for a given tool set.
4
5
  *
@@ -34,23 +35,39 @@ export function recommendLevel(tools, namespaceOf = defaultNamespaceOf) {
34
35
  const namespaceCount = namespaces.size;
35
36
  const opsPerNamespace = namespaceCount ? toolCount / namespaceCount : 0;
36
37
  const base = { toolCount, namespaceCount, opsPerNamespace };
37
- if (toolCount < 15) {
38
+ // Size the level-1 block by asking the library what it would actually emit, rather
39
+ // than reconstructing it — an earlier version rebuilt the schema by hand, kept the
40
+ // full descriptions where level 1 emits a signature line, and overestimated by ~34%.
41
+ //
42
+ // `countSchemaTokensApprox` counts characters. Measured against `count_tokens` on
43
+ // real MCP tools the ratio is a stable ~2.1 chars/token (1.98 at 10 tools, 2.15 at
44
+ // 149), so dividing gives a fair local estimate with no API call.
45
+ const CHARS_PER_TOKEN = 2.1;
46
+ const l1Tokens = Math.round(compress(tools, { level: 1 }).stats.compressedChars / CHARS_PER_TOKEN);
47
+ // The old heuristic gated level 3 on `opsPerNamespace >= 4`. That is a LEVEL-2
48
+ // question: level 2 pays dispatcher overhead per namespace, so its shape matters
49
+ // there. Level 3 uses one flat dispatcher and does not care about namespaces at all.
50
+ //
51
+ // The consequence was concrete: on the 149-tool real corpus (63 namespaces, 2.4 ops
52
+ // each) it recommended level 1 at 41,648 tokens when level 3 measures 2,980 — leaving
53
+ // ~38,700 tokens on the table. Measured on real tools, level 3 is smaller at EVERY
54
+ // count tested, down to 5 tools (635 vs 1,178).
55
+ //
56
+ // So size never argues for level 1. What argues for level 1 is that it keeps the
57
+ // provider's own schema enforcement, which levels 2-3 give up. That is worth more
58
+ // than a saving you would not notice — hence an absolute threshold on the block,
59
+ // not a shape test.
60
+ const THRESHOLD_TOKENS = 4000;
61
+ if (l1Tokens < THRESHOLD_TOKENS) {
38
62
  return {
39
63
  ...base,
40
64
  level: 1,
41
- reason: `Only ${toolCount} tools flattening schemas captures nearly all the available saving, and namespace collapse would add dispatcher overhead for little return.`,
42
- };
43
- }
44
- if (opsPerNamespace < 4) {
45
- return {
46
- ...base,
47
- level: 1,
48
- reason: `${toolCount} tools spread across ${namespaceCount} namespaces (${opsPerNamespace.toFixed(1)} ops each). The compound-dispatcher overhead is paid per namespace, so a set this sparse stays smaller at level 1.`,
65
+ reason: `The tool block is only ~${l1Tokens.toLocaleString()} tokens at level 1. Level 3 would be smaller, but not by enough to be worth giving up the provider's own argument validation, which level 1 keeps. Reach for level 3 when the block is large enough that reclaiming it changes what fits in your context.`,
49
66
  };
50
67
  }
51
68
  return {
52
69
  ...base,
53
70
  level: 3,
54
- reason: `${toolCount} tools across ${namespaceCount} namespaces (${opsPerNamespace.toFixed(1)} ops each) deep enough that a single dispatcher plus a cached code map beats per-tool definitions. Measured at ~82% fewer prompt tokens with no accuracy penalty across Opus 5, Sonnet 5 and Haiku 4.5, at the cost of roughly 0.6 extra turns and 1.7 lookup calls per task. Keep argument validation onon weaker models malformed arguments rise and validation is what catches them. If your workload is latency-critical rather than context-critical, drop to level 1.`,
71
+ reason: `${toolCount} tools take ~${l1Tokens.toLocaleString()} tokens at level 1; level 3 replaces them with two dispatcher tools plus a cached map. Measured on 149 real MCP tools: 41,648 tokens at level 1 against 2,980 at level 3, and 60/60 tasks completed across four frontier providers with zero hallucinated names. The cost is roughly 0.3-1.7 lookup calls per task so about half an extra turn plus the loss of provider-side argument checking keep \`validate\` on, which is what catches malformed arguments instead. If latency matters more than context, stay at level 1.`,
55
72
  };
56
73
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toolgz",
3
- "version": "0.2.5",
3
+ "version": "0.2.6",
4
4
  "description": "Compress LLM tool definitions to reclaim context window. Measured across Anthropic, OpenAI, Google and xAI.",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {