toolgz 0.2.8 → 0.2.10

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–39% and gives up nothing <code>recommendLevel()</code> picks for you.</em></p>
5
+ <p><em>On a large tool set, at level 3. The default (level 1) reclaims 13–39% and gives up nothing. <code>recommendLevel()</code> tells you which one your tools want; you pass its answer in.</em></p>
6
6
 
7
7
  <p>
8
8
  <a href="#measured-results">420-run cross-provider sweep</a> ·
@@ -15,6 +15,17 @@ zero runtime dependencies ·
15
15
  npm install toolgz
16
16
  ```
17
17
 
18
+ > ### 🤖 If a coding agent is reading this
19
+ >
20
+ > Point it at **[`llms.txt`](llms.txt)** — prescriptive integration instructions written
21
+ > for an agent, shipped inside the package at `node_modules/toolgz/llms.txt`. It covers
22
+ > the three-step contract, the rules that are not optional, per-model behaviour, and a
23
+ > `Do not` list of the mistakes we have actually fielded.
24
+ >
25
+ > ```
26
+ > Read node_modules/toolgz/llms.txt before integrating toolgz.
27
+ > ```
28
+
18
29
  ---
19
30
 
20
31
  ## The problem
@@ -33,7 +44,7 @@ Reclaiming the room is what this does.
33
44
  ```ts
34
45
  import { compress, recommendLevel, forAnthropic } from "toolgz";
35
46
 
36
- const { level } = recommendLevel(myTools); // 1 for small sets, 3 for large ones
47
+ const { level } = recommendLevel(myTools); // advice: 1 for a small block, 3 for a big one
37
48
  const c = compress(myTools, { level }); // your existing MCP/SDK tool array
38
49
  const { tools, system } = forAnthropic(c); // send these instead
39
50
  ```
@@ -41,7 +52,11 @@ const { tools, system } = forAnthropic(c); // send these instead
41
52
  `compress(myTools)` with no `level` gives you **level 1** — safe, native tool calling,
42
53
  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
54
  **level 3**, which is what `recommendLevel` returns once a tool set is big enough to
44
- amortise the dispatcher. Ask for it explicitly with `{ level: 3 }` if you prefer.
55
+ amortise the dispatcher.
56
+
57
+ **Note the shape of those three lines: `recommendLevel` only advises, and you pass its
58
+ answer back in.** Nothing upgrades itself — `compress(myTools)` is level 1 whether you
59
+ have 2 tools or 500. See [Which level to use](#which-level-to-use) for why.
45
60
 
46
61
  Then translate the model's call back before you dispatch:
47
62
 
@@ -226,6 +241,62 @@ tried and removed in 0.2.0 because they were smaller and still worse.
226
241
 
227
242
  ## Which level to use
228
243
 
244
+ ### The whole idea, in plain English
245
+
246
+ Your tool definitions are a **menu** handed to the model at the start of every single
247
+ conversation. It is long, and most of it is flowery prose about each dish.
248
+
249
+ - **Level 1** — the same menu with the prose cut. It is still a real menu: the model
250
+ points at a dish **by name**, and *the kitchen checks the order makes sense* before
251
+ cooking. This is the default, and it gives up nothing.
252
+ - **Level 3** — throw the menu away. Hand the model a **numbered list** and one waiter.
253
+ It says "number 12, no onions," and the waiter knows what that means. The list is
254
+ tiny. But the kitchen no longer checks the order — **toolgz checks it instead**,
255
+ against your original schema, and hands back a readable error if it's wrong.
256
+
257
+ If the model needs to know what number 12 comes with, it asks. That's the `q()` lookup,
258
+ and it costs about half a turn.
259
+
260
+ So: **level 1 is smaller. Level 3 is much smaller and you take over order-checking.**
261
+
262
+ ### Two things that surprise people
263
+
264
+ **1. Nothing changes level on its own. You are always the one who picks.**
265
+
266
+ ```ts
267
+ compress(myTools) // level 1. ALWAYS — 2 tools or 500.
268
+ compress(myTools, { level: 3 }) // level 3, because you asked for it
269
+
270
+ const { level } = recommendLevel(myTools); // just advice: returns 1 or 3
271
+ compress(myTools, { level }); // now it's 3, because you passed it in
272
+ ```
273
+
274
+ `recommendLevel()` **advises**; it does not act. On our 149-tool corpus,
275
+ `compress(myTools)` saves 45.2% and `compress(myTools, { level: 3 })` saves 96.5% — so
276
+ forgetting to pass the level back in quietly leaves half the win on the table.
277
+
278
+ This is deliberate. Level 3 gives up provider-side schema enforcement, and silently
279
+ changing a caller's correctness guarantees because their tool array grew would be a
280
+ worse bug than the tokens are worth.
281
+
282
+ **2. It switches on how *big* the block is, not how *many* tools you have.**
283
+
284
+ A tool can be 20 tokens or 460, so counting them tells you very little:
285
+
286
+ | Tool set | Level 1 block | Recommends |
287
+ |---|---:|:-:|
288
+ | 72 tools, one parameter each | ~5,000 tokens | **1** |
289
+ | 200 tools, one parameter each | ~14,100 tokens | **3** |
290
+ | **40** real MCP tools | ~10,600 tokens | **3** |
291
+ | 149 real MCP tools | ~42,700 tokens | **3** |
292
+
293
+ Forty chatty tools cross the line while 72 terse ones don't. The threshold is **10,000
294
+ tokens** — about 5% of a 200k window. Below that, reclaiming the block doesn't change
295
+ what fits, so keeping the provider's own argument validation is worth more than the
296
+ saving.
297
+
298
+ ### The levels in full
299
+
229
300
  Ask the library. It returns 1 or 3, never 2, and explains itself:
230
301
 
231
302
  ```ts
@@ -302,6 +373,11 @@ Level 3 has several map styles. Which one is cheapest turns out to depend on the
302
373
  model, so you can hand `compress()` a model id and let it use what was actually
303
374
  measured:
304
375
 
376
+ > **This is the one thing the library does choose for you, and only if you pass
377
+ > `model`.** Note the difference from levels: a map style is a pure encoding choice, so
378
+ > picking a better one cannot change your results. The level *can* — level 3 hands
379
+ > argument validation from the provider to toolgz — so that stays your explicit call.
380
+
305
381
  ```ts
306
382
  compress(myTools, { level: 3, model: "gpt-5.6-sol", objective: "cost" });
307
383
  ```
@@ -356,6 +432,10 @@ than a bug report from you.
356
432
  npx tsx examples/01-minimal.ts
357
433
  ```
358
434
 
435
+ **[`examples/README.md`](examples/README.md) walks through all five** with their real
436
+ output and what each one is trying to teach — start there if you would rather read than
437
+ run.
438
+
359
439
  | File | Shows |
360
440
  |---|---|
361
441
  | [`01-minimal.ts`](examples/01-minimal.ts) | the smallest useful thing: `recommendLevel` → `compress` → `resolve` |
@@ -783,8 +863,13 @@ Use `forGemini`, which strips the keywords Gemini won't accept. If a new one
783
863
  appears, it's a one-line addition to the adapter.
784
864
 
785
865
  **Savings look small.**
786
- Check tool count and shape with `recommendLevel(myTools)`. Under ~15 tools
787
- there's little to reclaim. Also check `c.stats`:
866
+ First check you actually passed a level: `compress(myTools)` is **level 1**, and it
867
+ stays level 1 no matter how many tools you hand it. `recommendLevel` advises, it does
868
+ not act — you have to pass its answer in as `{ level }`.
869
+
870
+ If you did, ask `recommendLevel(myTools)` and read the `reason`. It reports the size of
871
+ your level-1 block, and under ~10,000 tokens there is little worth reclaiming. Also
872
+ check `c.stats`:
788
873
 
789
874
  ```ts
790
875
  console.log(c.stats);
@@ -885,7 +970,15 @@ Returns:
885
970
 
886
971
  ### `recommendLevel(tools, namespaceOf?) → Recommendation`
887
972
 
888
- `{ level, reason, toolCount, namespaceCount, opsPerNamespace }`. Returns 1 or 3.
973
+ `{ level, reason, toolCount, namespaceCount, opsPerNamespace }`. Returns 1 or 3, never 2.
974
+
975
+ **It advises; it does not act.** Pass the answer back in yourself —
976
+ `compress(tools, { level })`. Calling `compress(tools)` alone is level 1 regardless of
977
+ how large `tools` is.
978
+
979
+ The decision is on the **size** of the level-1 block (threshold 10,000 tokens ≈ 5% of a
980
+ 200k window), not on `toolCount`. The three shape fields are reported for your own
981
+ logging; only block size drives the level.
889
982
 
890
983
  ### Provider adapters
891
984
 
@@ -974,8 +1067,13 @@ xAI is OpenAI-compatible — use `forOpenAI` with `baseURL: "https://api.x.ai/v1
974
1067
  - **It has not been measured on a non-frontier model at level 3.** On Haiku 4.5, argument
975
1068
  errors rose sharply (17 of 30 runs) — all caught and retried, no task lost, but that is the
976
1069
  known edge.
977
- - **It is not magic on ten tools.** Under ~15 tools there is little to reclaim;
978
- `recommendLevel()` will tell you so.
1070
+ - **It is not magic on a small tool block.** Under ~10,000 tokens at level 1 there is
1071
+ little worth reclaiming, and `recommendLevel()` will say so and keep you on level 1.
1072
+ That is usually a small number of tools, but not always — it depends on how verbose
1073
+ your schemas are, not on the count.
1074
+ - **It does not pick a level for you.** `compress()` defaults to level 1 and stays there;
1075
+ reaching level 3 is always an explicit `{ level }`. Deliberate — level 3 trades away
1076
+ provider-side schema enforcement, and that is not a trade to make behind your back.
979
1077
 
980
1078
  ## Determinism
981
1079
 
@@ -31,9 +31,19 @@ export type Recommendation = {
31
31
  * slower and dearer than level 3. It is never recommended. It stays in the
32
32
  * API for callers who need real operation names on the wire.
33
33
  *
34
- * The level 1 → 3 threshold is driven by tools-per-namespace, not tool count:
35
- * the compound-dispatcher overhead is paid per namespace, so a wide, sparse
36
- * tool set stays cheaper at level 1 even when the total count is large.
34
+ * The level 1 → 3 threshold is an absolute size test on the level-1 block
35
+ * neither tool count nor namespace shape. A tool is 20 to 460 tokens depending
36
+ * on how verbose its schema is, so 40 real MCP tools cross the line while 72
37
+ * one-parameter tools do not.
38
+ *
39
+ * An earlier version gated on tools-per-namespace. That is a *level 2* question
40
+ * (level 2 pays dispatcher overhead per namespace); level 3 uses one flat
41
+ * dispatcher and is indifferent to shape. See the comment on THRESHOLD_TOKENS.
42
+ *
43
+ * This function only ever *advises*. `compress()` defaults to level 1 and stays
44
+ * there unless the caller passes a level explicitly — level 3 trades away the
45
+ * provider's own schema enforcement, and that is not a trade to make on a
46
+ * caller's behalf because their tool array grew.
37
47
  */
38
48
  export declare function recommendLevel(tools: Tool[], namespaceOf?: (n: string) => {
39
49
  ns: string;
package/dist/recommend.js CHANGED
@@ -25,9 +25,19 @@ import { compress } from "./index.js";
25
25
  * slower and dearer than level 3. It is never recommended. It stays in the
26
26
  * API for callers who need real operation names on the wire.
27
27
  *
28
- * The level 1 → 3 threshold is driven by tools-per-namespace, not tool count:
29
- * the compound-dispatcher overhead is paid per namespace, so a wide, sparse
30
- * tool set stays cheaper at level 1 even when the total count is large.
28
+ * The level 1 → 3 threshold is an absolute size test on the level-1 block
29
+ * neither tool count nor namespace shape. A tool is 20 to 460 tokens depending
30
+ * on how verbose its schema is, so 40 real MCP tools cross the line while 72
31
+ * one-parameter tools do not.
32
+ *
33
+ * An earlier version gated on tools-per-namespace. That is a *level 2* question
34
+ * (level 2 pays dispatcher overhead per namespace); level 3 uses one flat
35
+ * dispatcher and is indifferent to shape. See the comment on THRESHOLD_TOKENS.
36
+ *
37
+ * This function only ever *advises*. `compress()` defaults to level 1 and stays
38
+ * there unless the caller passes a level explicitly — level 3 trades away the
39
+ * provider's own schema enforcement, and that is not a trade to make on a
40
+ * caller's behalf because their tool array grew.
31
41
  */
32
42
  export function recommendLevel(tools, namespaceOf = defaultNamespaceOf) {
33
43
  const namespaces = new Set(tools.map((t) => namespaceOf(t.name).ns));
package/llms.txt ADDED
@@ -0,0 +1,205 @@
1
+ # toolgz
2
+
3
+ > Compresses LLM tool definitions to reclaim context window. Zero runtime dependencies,
4
+ > TypeScript, Apache-2.0. Works with any model and any framework: it transforms a tool
5
+ > array before you send it, and translates the model's call back afterwards.
6
+
7
+ **This file is written for a coding agent integrating toolgz into a project.** It is
8
+ prescriptive on purpose. If you are contributing to toolgz itself, read `AGENTS.md` in the
9
+ repository instead — different audience, different rules.
10
+
11
+ Installed copy lives at `node_modules/toolgz/llms.txt`. Full prose docs: `README.md`.
12
+
13
+ ---
14
+
15
+ ## The whole integration, correctly
16
+
17
+ ```ts
18
+ import { compress, recommendLevel, forAnthropic } from "toolgz";
19
+
20
+ // 1. Decide the level ONCE, at startup — not per request.
21
+ const { level } = recommendLevel(myTools);
22
+ const c = compress(myTools, { level });
23
+
24
+ // 2. Send c.tools instead of your tool array, and append the preamble to your system prompt.
25
+ const { tools, system } = forAnthropic(c);
26
+
27
+ // 3. Translate every tool call back before you dispatch.
28
+ for (const block of res.content.filter((b) => b.type === "tool_use")) {
29
+ const r = c.resolve(block.name, block.input);
30
+ if (r.kind === "call") {
31
+ const out = await myDispatch(r.name, r.args); // real name, real args
32
+ results.push({ type: "tool_result", tool_use_id: block.id, content: JSON.stringify(out) });
33
+ } else if (r.kind === "meta") {
34
+ // toolgz answered a lookup itself. DO NOT dispatch. Return its text to the model.
35
+ results.push({ type: "tool_result", tool_use_id: block.id, content: r.result });
36
+ } else {
37
+ // Recoverable by design: hand the message back and the model retries.
38
+ results.push({ type: "tool_result", tool_use_id: block.id, content: `Error: ${r.message}`, is_error: true });
39
+ }
40
+ }
41
+ ```
42
+
43
+ Those three steps are the entire contract. `myDispatch` receives exactly what it received
44
+ before toolgz existed.
45
+
46
+ ## Rules that are not optional
47
+
48
+ 1. **`resolve()` every tool call.** At level 3 the model emits opaque codes (`t(f="a0")`),
49
+ so dispatching `block.name` directly will fail or dispatch the wrong tool. Route
50
+ everything through `resolve()` even at level 0, so the level becomes a one-line change.
51
+
52
+ 2. **Handle all three `kind` values.** `call` → dispatch. `meta` → toolgz already answered
53
+ a lookup; return `r.result` to the model and dispatch nothing. `error` → return
54
+ `r.message` to the model as an error tool_result; do **not** throw. The `error` path is
55
+ the recovery mechanism, not a failure.
56
+
57
+ 3. **Send the system preamble.** At level 3 the tool map lives in the system prompt. Drop
58
+ it and the model has no idea what any code means. It is `""` at levels 0–1, so
59
+ appending it unconditionally is always safe.
60
+
61
+ 4. **`compress()` never changes level on its own.** `compress(tools)` is level 1 forever,
62
+ 2 tools or 500. `recommendLevel()` *advises*; you pass its answer in as `{ level }`.
63
+ Deliberate: level 3 moves argument validation from the provider to toolgz, which is not
64
+ a change to make behind a caller's back.
65
+
66
+ 5. **Leave `validate` on** (the default). Levels 2–3 give up the provider's constrained
67
+ decoding, so toolgz validating against the original schema is what catches malformed
68
+ arguments. On a weak model, disabling it converts roughly half of all runs from a
69
+ recovered retry into a bad dispatch.
70
+
71
+ 6. **Compress once, reuse the result.** `compress()` is deterministic — same tools in,
72
+ byte-identical payload out — so the output is prompt-cache friendly. Recomputing per
73
+ request is wasted work; changing the tool list mid-conversation invalidates the cache
74
+ and strands codes the model has already seen.
75
+
76
+ ## Choosing a level
77
+
78
+ Ask, don't guess: `recommendLevel(myTools)` returns `{ level, reason }` — 1 or 3, never 2.
79
+
80
+ | Level | Wire | Names | Provider schema enforcement | When |
81
+ |:-:|---|:-:|:-:|---|
82
+ | 0 | unchanged | real | yes | A/B control only |
83
+ | **1** | one native tool each, flattened schemas | real | **yes** | **default.** Reclaims 13–39%, gives up nothing |
84
+ | 2 | one tool per namespace | real | no | only if you need real op names on the wire |
85
+ | **3** | one dispatcher + one lookup | codes | no | **large tool sets.** Up to ~85–96% |
86
+
87
+ The switch is the **size** of the level-1 block, not the tool count — threshold 10,000
88
+ tokens (~5% of a 200k window). A tool is 20 to 460 tokens depending on schema verbosity,
89
+ so 40 real MCP tools cross it while 72 one-parameter tools do not.
90
+
91
+ Level 2 is dominated by level 3 on accuracy (six times the malformed arguments) and is
92
+ never recommended. It stays in the API for callers who need real operation names.
93
+
94
+ ## Provider adapters — pick the right one
95
+
96
+ ```ts
97
+ forAnthropic(c, { cache?, ttl? }) // → { tools, system } Messages API
98
+ forOpenAI(c) // → { tools, systemPreamble } /v1/chat/completions
99
+ forOpenAIResponses(c) // → { tools, systemPreamble } /v1/responses
100
+ forGemini(c) // → { tools, systemPreamble } generateContent
101
+ ```
102
+
103
+ Two mistakes that produce confusing provider errors:
104
+
105
+ - **OpenAI has two incompatible tool shapes.** `/v1/chat/completions` wants nested
106
+ `{type, function:{…}}`; `/v1/responses` wants flat `{type, name, …}`. If you set
107
+ reasoning effort on a GPT-5.x model you must use `/v1/responses`, therefore
108
+ `forOpenAIResponses`. Using the wrong adapter is rejected at the API.
109
+ - **Gemini returns ONE wrapper object,** not one entry per tool. Read
110
+ `tools[0].functionDeclarations.length`; `tools.length` is always 1.
111
+
112
+ `forGemini` also repairs three schema forms Gemini rejects with a 400: an array-valued
113
+ `type`, `type: "array"` with no `items`, and a non-string `enum`. Constraints stripped to
114
+ satisfy Gemini are still enforced by `resolve()` against your original schema — nothing is
115
+ silently dropped.
116
+
117
+ xAI is OpenAI-compatible: use `forOpenAI` with `baseURL: "https://api.x.ai/v1"`. **xAI
118
+ caps tool arrays at 350**; other providers accept 1,200+. Level 3 sends 2 either way.
119
+
120
+ ## Model-specific behaviour
121
+
122
+ Pass an exact model id and toolgz uses the best *measured* map style for it:
123
+
124
+ ```ts
125
+ compress(myTools, { level: 3, model: "gpt-5.6-sol", objective: "cost" });
126
+ ```
127
+
128
+ | Model | `objective: "cost"` | Measured vs default |
129
+ |---|---|---:|
130
+ | `gpt-5.6-sol` | `explicit` | −20.7% |
131
+ | `gemini-3.1-pro-preview` | `explicit` | −15.4% |
132
+ | `claude-opus-5` | `explicit` | −9.0% |
133
+ | `grok-4.5` | *(no entry)* | `explicit` is **+13.2%** there |
134
+
135
+ - Model ids are **exact, never families**. A result for `gpt-5.6-sol` says nothing about
136
+ `gpt-5.7`. An unknown model gets the conservative default — absence of evidence, not a
137
+ prediction.
138
+ - The default objective is `occupancy`, which has **no** entries: nothing beat the default
139
+ by more than 3.1% on that axis, under the 5% effect-size floor. Only `cost` has rows.
140
+ - This is the one thing the library picks for you, and only when you pass `model`. A map
141
+ style is a pure encoding choice, so a better one cannot change results. The *level*
142
+ can — which is why that stays explicit.
143
+ - Do not set `mapStyle` by hand unless you have measured your own workload. Prefer
144
+ `model` + `objective`.
145
+
146
+ ## MCP servers
147
+
148
+ `tools/list` returns `{ name, description, inputSchema }` per tool — exactly what
149
+ `compress()` accepts. **There is no adapter layer.** Merge the arrays from every server
150
+ and pass them in.
151
+
152
+ `input_schema` and `inputSchema` are both accepted, so Anthropic-shaped and MCP-shaped
153
+ tools can be mixed.
154
+
155
+ Watch for name collisions when merging catalogues from independent servers. If two tool
156
+ names normalise identically, toolgz refuses to alias either one rather than guess — the
157
+ map code still works.
158
+
159
+ ## API surface
160
+
161
+ ```ts
162
+ compress(tools, options?) → CompressResult
163
+ recommendLevel(tools, namespaceOf?) → { level, reason, toolCount, namespaceCount, opsPerNamespace }
164
+ selectMapStyle(options) → { mapStyle, requestedMapStyle?, fallbackReason? } // pure
165
+ ```
166
+
167
+ `CompressOptions`: `level`, `mapStyle`, `namespaceOf`, `aliasOf`, `searchLimit`,
168
+ `validate`, `model`, `objective`.
169
+
170
+ `CompressResult`: `tools`, `systemPreamble`, `cachePreamble`, `resolve()`, `codeFor()`,
171
+ `encodeCallForTest()`, `stats`.
172
+
173
+ `stats`: `level`, `mapStyle`, `requestedMapStyle`, `fallbackReason`, `toolCount`,
174
+ `wireToolCount`, `originalChars`, `compressedChars`, `savedPct`.
175
+
176
+ `namespaceOf` must return `{ ns, op }`, **not** a bare string. Returning a string throws
177
+ with a corrected example — it used to fail silently and produce a plausible-looking but
178
+ wrong map.
179
+
180
+ ## Things to tell the user, not assume
181
+
182
+ - **`savedPct` is a CHARACTER saving**, a few points optimistic against tokens. Providers
183
+ charge a fixed framing cost per tool definition that character counting cannot see. For
184
+ any published figure, measure with the provider's own token counter.
185
+ - **Never use `tiktoken` to count Claude tokens** — it is OpenAI's tokenizer and is wrong
186
+ for Claude by 15–20%+. Use Anthropic's `count_tokens`.
187
+ - **Token saving ≠ cost saving.** Prompt caching already makes tool tokens cheap; the claim
188
+ here is context-window *occupancy*. Cost follows by a variable amount, and on OpenAI —
189
+ where reasoning output dominates the bill — it was as little as 7%.
190
+ - **Level 3 costs turns.** Roughly 0.3–1.7 lookup calls per task, about half an extra turn.
191
+ If latency matters more than context, stay at level 1.
192
+ - **Below the frontier tier, argument formatting degrades at level 3.** On Haiku 4.5, 17 of
193
+ 30 runs produced a malformed argument — all caught by `validate` and recovered, no task
194
+ lost, but that is the known edge. Tool *choice* did not degrade.
195
+
196
+ ## Do not
197
+
198
+ - Do not dispatch `block.name` without `resolve()`.
199
+ - Do not throw on `kind: "error"` — return it to the model.
200
+ - Do not dispatch on `kind: "meta"`.
201
+ - Do not omit `systemPreamble` at level 3.
202
+ - Do not set `validate: false` to silence a malformed-argument error; fix the schema.
203
+ - Do not call `compress()` inside the request loop.
204
+ - Do not report `savedPct` as a token or cost saving.
205
+ - Do not assume a level was chosen for you.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toolgz",
3
- "version": "0.2.8",
3
+ "version": "0.2.10",
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": {
@@ -45,6 +45,7 @@
45
45
  "files": [
46
46
  "dist",
47
47
  "README.md",
48
+ "llms.txt",
48
49
  "LICENSE",
49
50
  "NOTICE"
50
51
  ],