@kunal_dutt/token-lens 0.0.0-stage → 0.2.16
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/GATEWAY.md +491 -0
- package/LICENSE +21 -0
- package/PRIVACY.md +36 -0
- package/README.md +80 -2
- package/agent-cost-check.yml +49 -0
- package/check-agent.mjs +3298 -0
- package/cli.mjs +22 -0
- package/gateway.mjs +3365 -0
- package/package.json +45 -4
- package/sdk.mjs +207719 -0
- package/server.mjs +3392 -0
- package/token-lens.mcpb +0 -0
- package/types/src/core/advice.d.ts +27 -0
- package/types/src/core/budget.d.ts +28 -0
- package/types/src/core/compress.d.ts +29 -0
- package/types/src/core/cost.d.ts +32 -0
- package/types/src/core/guard.d.ts +20 -0
- package/types/src/core/hotspots.d.ts +124 -0
- package/types/src/core/index.d.ts +74 -0
- package/types/src/core/media.d.ts +29 -0
- package/types/src/core/models.d.ts +81 -0
- package/types/src/core/plans.d.ts +88 -0
- package/types/src/core/redos.d.ts +32 -0
- package/types/src/core/review.d.ts +46 -0
- package/types/src/core/rules.d.ts +47 -0
- package/types/src/core/tasks.d.ts +44 -0
- package/types/src/core/tokens.d.ts +19 -0
- package/types/src/core/verify.d.ts +19 -0
- package/types/src/gateway/config.d.ts +217 -0
- package/types/src/gateway/guard.d.ts +56 -0
- package/types/src/gateway/images.d.ts +40 -0
- package/types/src/gateway/metrics.d.ts +287 -0
- package/types/src/gateway/optimize.d.ts +102 -0
- package/types/src/gateway/protocol.d.ts +68 -0
- package/types/src/gateway/runs.d.ts +182 -0
- package/types/src/gateway/shared.d.ts +57 -0
- package/types/src/gateway/signals.d.ts +38 -0
- package/types/src/gateway/storage.d.ts +88 -0
- package/types/src/gateway/verify.d.ts +51 -0
- package/types/src/sdk/index.d.ts +228 -0
package/GATEWAY.md
ADDED
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
# Token Lens gateway
|
|
2
|
+
|
|
3
|
+
A self-hosted gateway that cuts the tokens your AI features, workflows and
|
|
4
|
+
agents spend, on any model: OpenAI, Anthropic, Azure OpenAI, or a model you
|
|
5
|
+
run yourself behind an OpenAI-compatible server (vLLM, TGI, SGLang, Ollama,
|
|
6
|
+
LiteLLM, an in-house serving stack).
|
|
7
|
+
|
|
8
|
+
It speaks the OpenAI and Anthropic APIs, so nothing in your app changes except
|
|
9
|
+
its **base URL**. It runs inside your own network; prompts never leave it except
|
|
10
|
+
to the upstream you configure, and it stores no prompt text (logs and reports
|
|
11
|
+
hold counts only).
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
your app / agent / workflow ──► Token Lens gateway ──► OpenAI · Anthropic · Azure · your own model
|
|
15
|
+
(any language, any SDK) optimize · measure · report
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
**Success criterion: fewer tokens, same work.** The gateway never changes the
|
|
19
|
+
model a call asks for and never suggests a smaller one. It cuts what the agent
|
|
20
|
+
sends (repeated prompts, history, tool definitions and results, retries), shows
|
|
21
|
+
the saving in tokens and money before and after, and backs it with output
|
|
22
|
+
checks: sampled calls are re-run as originally written and the answers compared.
|
|
23
|
+
|
|
24
|
+
## Run it
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
docker build -t token-lens-gateway token-lens
|
|
28
|
+
docker run -p 8080:8080 \
|
|
29
|
+
-e OPENAI_API_KEY=sk-... -e ANTHROPIC_API_KEY=sk-ant-... \
|
|
30
|
+
-e TOKEN_LENS_CLIENT_KEYS="payments-team:tl-key-1,support-agent:tl-key-2" \
|
|
31
|
+
token-lens-gateway
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
or without Docker (Node 20+): `npm install && npm run build:gateway && node dist-gateway/gateway.mjs`.
|
|
35
|
+
The bundle is one file with no runtime dependencies.
|
|
36
|
+
|
|
37
|
+
For several upstreams, routing and your own models, mount a config file and set
|
|
38
|
+
`TOKEN_LENS_CONFIG` (see [deploy/gateway.example.json](deploy/gateway.example.json)
|
|
39
|
+
and [deploy/docker-compose.yml](deploy/docker-compose.yml)).
|
|
40
|
+
|
|
41
|
+
## Point your app at it
|
|
42
|
+
|
|
43
|
+
| Client | Change |
|
|
44
|
+
| --- | --- |
|
|
45
|
+
| OpenAI SDK (Python, Node, Java, Go, .NET…) | `base_url="http://token-lens:8080/v1"`, `api_key=<your Token Lens client key>` |
|
|
46
|
+
| Anthropic SDK | `base_url="http://token-lens:8080"`, `api_key=<client key>` |
|
|
47
|
+
| LangChain / LangGraph | `ChatOpenAI(base_url=…/v1)` or `ChatAnthropic(base_url=…)` |
|
|
48
|
+
| LlamaIndex, CrewAI, AutoGen, Semantic Kernel, Vercel AI SDK, Spring AI | their OpenAI or Anthropic provider's base URL setting |
|
|
49
|
+
| OpenAI Agents SDK | `AsyncOpenAI(base_url=…/v1)` as the client (Responses API is supported) |
|
|
50
|
+
| Anything else | `OPENAI_BASE_URL` / `ANTHROPIC_BASE_URL` environment variables, or raw HTTP |
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
from openai import OpenAI
|
|
54
|
+
client = OpenAI(base_url="http://token-lens:8080/v1", api_key="tl-key-1")
|
|
55
|
+
client.chat.completions.create(model="acme-llm-70b", messages=[...]) # routed to your own model
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Endpoints optimized: `/v1/chat/completions`, `/v1/responses`, `/v1/messages`,
|
|
59
|
+
streaming included. Every other `/v1/...` path (models, embeddings, files,
|
|
60
|
+
batches, count_tokens) is passed through unchanged.
|
|
61
|
+
|
|
62
|
+
### One app, any model: calls between the two APIs
|
|
63
|
+
|
|
64
|
+
An app written with the Anthropic SDK can call a model behind an
|
|
65
|
+
OpenAI-compatible API (an in-house model on vLLM, Azure, any OpenAI-style
|
|
66
|
+
server), and an app written with the OpenAI SDK can call Claude. Route the model
|
|
67
|
+
to the upstream in the config (`"routes": [{ "model": "acme-*", "upstream": "inhouse" }]`)
|
|
68
|
+
and keep calling the endpoint your SDK uses.
|
|
69
|
+
|
|
70
|
+
- **What is translated:** system prompts, text, images, tool definitions, tool
|
|
71
|
+
calls and results, `tool_choice`, stop sequences, temperature and token limits,
|
|
72
|
+
in both directions. Answers, usage (including cached tokens) and errors come
|
|
73
|
+
back in your SDK's shape, and streams are translated event by event.
|
|
74
|
+
- **What stays the same:** the model. Every Token Lens fix and output check works
|
|
75
|
+
the same on translated calls. The gateway's prompt caching becomes Anthropic
|
|
76
|
+
cache breakpoints when an OpenAI-style call goes to Claude.
|
|
77
|
+
- **Keys:** if callers bring their own key, it is sent the way the other API
|
|
78
|
+
expects (`Authorization: Bearer` becomes `x-api-key`, and the reverse).
|
|
79
|
+
|
|
80
|
+
## Your own model
|
|
81
|
+
|
|
82
|
+
A company model isn't in anyone's built-in list, so register it: its context
|
|
83
|
+
window, tokenizer and price. Matching is by model id, so `acme-llm` also covers
|
|
84
|
+
`acme-llm-70b-instruct`.
|
|
85
|
+
|
|
86
|
+
```json
|
|
87
|
+
"models": [
|
|
88
|
+
{ "id": "acme-llm", "match": ["acme-llm", "/^acme-.*-instruct$/"], "contextWindow": 32768,
|
|
89
|
+
"tokenizer": "heuristic", "charsPerToken": 3.6,
|
|
90
|
+
"pricing": { "inputPerMTok": 0.2, "outputPerMTok": 0.6 } }
|
|
91
|
+
],
|
|
92
|
+
"upstreams": { "inhouse": { "type": "openai", "baseUrl": "http://llm.internal:8000/v1" } },
|
|
93
|
+
"routes": [{ "model": "acme-*", "upstream": "inhouse" }]
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
- `tokenizer`: `"o200k"` if the model uses OpenAI's o200k tokenizer, otherwise
|
|
97
|
+
`"heuristic"`. Heuristic counts **calibrate themselves**: each time the
|
|
98
|
+
upstream reports usage, the gateway compares it with its estimate and adjusts
|
|
99
|
+
that model's factor (see `calibration` in `/token-lens/stats`).
|
|
100
|
+
- The same entry can override a built-in model, for example to add exact pricing
|
|
101
|
+
for `gpt-4o-mini`.
|
|
102
|
+
- Models can also be added at runtime: `POST /token-lens/models` with the admin
|
|
103
|
+
key (`TOKEN_LENS_ADMIN_KEY`). Runtime entries live in memory; put permanent
|
|
104
|
+
ones in the config file.
|
|
105
|
+
|
|
106
|
+
## What it does to each request
|
|
107
|
+
|
|
108
|
+
The gateway sorts every request's input into parts: **system** prompt,
|
|
109
|
+
**tools** definitions, **history**, **tool outputs**, **context** (material
|
|
110
|
+
pasted or retrieved into the newest message) and the **prompt** itself. It
|
|
111
|
+
counts each part in the model's own tokens, tracks the parts across the calls
|
|
112
|
+
of a run, and applies the fix that fits the part that is growing.
|
|
113
|
+
|
|
114
|
+
Changes that keep the meaning are applied automatically:
|
|
115
|
+
|
|
116
|
+
| Fix | Part | What it does |
|
|
117
|
+
| --- | --- | --- |
|
|
118
|
+
| `prompt` | user messages | The Token Lens optimizer, adapted to the model and task. Code, quotes, URLs, XML-tagged and pasted material stay verbatim. Every user message is rewritten the same way on every call, so the model sees one consistent history and prompt caches keep hitting. |
|
|
119
|
+
| `minify-json` | tool outputs | Pretty-printed JSON re-serialized without whitespace. Same data. |
|
|
120
|
+
| `collapse-repeats` | tool outputs | Runs of identical lines become one line plus `[previous line repeated N more times]`. |
|
|
121
|
+
| `dedupe-tool-outputs` | tool outputs | When the agent gets the same result twice (same tool, same input), the result is sent once; later copies point back to the first, which stays in place so caching still works. |
|
|
122
|
+
| `dedupe-passages` | context, history, tool outputs | A paragraph of 200+ characters repeated within a request (the same retrieved chunk, a policy pasted twice) is sent once. Later copies point back to the first. |
|
|
123
|
+
| `dedupe-images` | images | An image sent again later in the conversation (a repeated screenshot or upload) is sent once. Later copies become a one-line pointer to the first. |
|
|
124
|
+
| `prompt-caching` | system, tools, history | Once a run re-sends a prefix of 1,024+ tokens: Anthropic cache breakpoints after the tools, the system prompt and the conversation; OpenAI's `prompt_cache_key` per run. Cached input costs about a tenth. Not added to one-off calls, where a cache write would cost more. |
|
|
125
|
+
| retry replay | whole call | An identical, non-streamed, temperature-0 request within 2 minutes gets the earlier answer, at no token cost. Sampled requests (temperature unset or > 0) are always sent. |
|
|
126
|
+
| embeddings cache | `/v1/embeddings` | A text this team already embedded with the same model and settings is answered from memory; only new texts go upstream. See [Embeddings](#embeddings-unchanged-documents-once). |
|
|
127
|
+
|
|
128
|
+
Changes that could change the answer wait for an opt-in, either for everyone
|
|
129
|
+
in the config's `optIn` or per request with `x-token-lens-allow`:
|
|
130
|
+
|
|
131
|
+
| Opt-in | What it does |
|
|
132
|
+
| --- | --- |
|
|
133
|
+
| `elide-tool-outputs` | Tool outputs older than the newest 3 (and over 300 tokens) are replaced with a one-line stub. |
|
|
134
|
+
| `lazy-tools` | Loads tool definitions only when the agent needs them (the idea behind Atlassian's mcp-compressor). See [Large tool lists](#large-tool-lists-lazy-tools). |
|
|
135
|
+
| `trim-tool-fields` | In tool outputs older than the newest 3, JSON fields the agent never referred to afterwards (no value and not the field's name appears in any later message or tool call) are left out and listed in `_omitted_by_token_lens`. Small fields stay. |
|
|
136
|
+
| `compact-json` | In JSON tool outputs, a list of 5+ records with the same keys is written once as `{"_columns": [...], "_rows": [[...], ...]}` instead of repeating every key in every record. Nothing is dropped. |
|
|
137
|
+
| `park-tool-outputs` | Older tool outputs over 1,500 tokens stay outside the conversation: their first lines and an id remain, plus a `token_lens_read_output` tool the model calls to search them or read a slice. The gateway answers those look-ups itself and re-sends, so the app only gets the model's real answer; after 3 look-ups the request goes again with the outputs in full. Non-streamed OpenAI Chat and Anthropic requests. |
|
|
138
|
+
| `flex` | The provider's cheaper, slower processing on the same model: OpenAI's `service_tier: "flex"`, about half price. Offered on api.openai.com upstreams (set `"flex": true` on others that support it). If flex is busy or not offered for the model, the same request goes again at the normal tier. Doesn't change the request's content. |
|
|
139
|
+
| `warm-cache` | When several calls with the same 1,024+ token prefix arrive at once, the first goes alone and the rest wait for it (up to 15 seconds, `optIn.warmCache.maxWaitMs`), so they read its cache instead of each paying for the prefix. Trades a little speed; doesn't change requests. |
|
|
140
|
+
| run budget | A cap per run on calls and/or spend (`runBudget` in the config, or `x-token-lens-max-calls` / `x-token-lens-max-cost-usd`). Calls past the cap get an error instead of being sent, which stops a looping agent. |
|
|
141
|
+
|
|
142
|
+
Per-request controls (headers): `x-token-lens-mode: optimize | observe | off`,
|
|
143
|
+
`x-token-lens-level: light | balanced | aggressive`, `x-token-lens-allow`,
|
|
144
|
+
`x-token-lens-run` (names the run; otherwise calls are grouped by app, model,
|
|
145
|
+
system prompt and first user message), `x-token-lens-app` (when no client keys
|
|
146
|
+
are configured).
|
|
147
|
+
|
|
148
|
+
Every response carries `x-token-lens-run`, `-profile`, `-input-tokens`,
|
|
149
|
+
`-saved-tokens`, `-actions`, `-tips` and `-growing` (the part that grew most in
|
|
150
|
+
this run). `x-token-lens-price-tier: near 200000` or `above 200000` warns that a
|
|
151
|
+
call is close to or past the size where its model bills the whole request at a
|
|
152
|
+
higher rate (see [Price tiers](#price-tiers)).
|
|
153
|
+
|
|
154
|
+
## Savings report: tokens and money, before and after
|
|
155
|
+
|
|
156
|
+
`GET /token-lens/report` is a page anyone can open in a browser (with
|
|
157
|
+
`?key=<client key>` when keys are set): tokens and cost before and after, money
|
|
158
|
+
saved, and the share of checked calls whose output was unchanged, broken down by
|
|
159
|
+
team, agent, model and run. `GET /token-lens/report.json` has the same data.
|
|
160
|
+
|
|
161
|
+
- **Before** is what the same calls would have cost without Token Lens: the
|
|
162
|
+
tokens it removed added back, cached input priced as normal input, and
|
|
163
|
+
replayed retries paid in full. **After** is what the provider billed, with
|
|
164
|
+
cached reads and cache writes at their own rates.
|
|
165
|
+
- Built-in model families carry public API list prices. For your own models, and
|
|
166
|
+
to use your negotiated rates, give the model `pricing` in the config. Calls on
|
|
167
|
+
models without a price are counted in tokens only, and the report says how many.
|
|
168
|
+
- Name the agent or workflow with the `x-token-lens-agent` header to get a row
|
|
169
|
+
per agent; the team is the client key's name.
|
|
170
|
+
|
|
171
|
+
## Output checks: evidence the work is unchanged
|
|
172
|
+
|
|
173
|
+
For a sample of calls the gateway changed, it also sends the original request
|
|
174
|
+
to the same model in the background and compares the two answers. Streamed
|
|
175
|
+
calls are checked too: the streamed answer is put back together and compared
|
|
176
|
+
with an unstreamed original.
|
|
177
|
+
|
|
178
|
+
- A check passes when the tool calls are identical (same tools, same arguments)
|
|
179
|
+
and the text says the same thing (word and word-pair overlap of at least
|
|
180
|
+
`minSimilarity`, 0.8 by default). A changed number counts as a different answer.
|
|
181
|
+
- `verify.sampleRate` (default 5%; `TOKEN_LENS_VERIFY_RATE`) sets how many
|
|
182
|
+
calls are checked; calls using an opt-in fix are checked at least 25% of the
|
|
183
|
+
time. Checks cost a call each, so after the first 5 per team and agent they
|
|
184
|
+
stop whenever check tokens would exceed 20% of the tokens saved
|
|
185
|
+
(`maxShareOfSavings`). Their cost is shown separately and the report's net
|
|
186
|
+
saving subtracts it.
|
|
187
|
+
- `x-token-lens-verify: 1` checks a specific call. Results appear per run
|
|
188
|
+
(`outputCheck`, `outputChecks`), in the report and in `/metrics`.
|
|
189
|
+
- Even unchanged requests don't always get word-for-word identical answers when
|
|
190
|
+
sampling is on, so treat the rate as a lower bound on how often the work is the
|
|
191
|
+
same; temperature-0 calls give the cleanest signal.
|
|
192
|
+
|
|
193
|
+
### The rule: no fix may change an answer
|
|
194
|
+
|
|
195
|
+
- **A changed answer switches the fix off.** When a check finds a different
|
|
196
|
+
answer, the gateway asks the model the original request once more. If the
|
|
197
|
+
model's own two answers agree, the fixes that were in play are switched off for
|
|
198
|
+
that team and agent (the log says which). If the model's own answers differ just
|
|
199
|
+
as much, the difference isn't the fixes' and they stay on (the check shows
|
|
200
|
+
`modelVaries: true`). Switched-off fixes are listed at `GET /token-lens/guard`,
|
|
201
|
+
in `/token-lens/stats` and in the report, and stay off until an admin calls
|
|
202
|
+
`POST /token-lens/guard/reset` (body `{}` for all, or `{"app": "...", "agent": "..."}`).
|
|
203
|
+
- **Opt-in fixes prove themselves first.** `elide-tool-outputs` and `lazy-tools`
|
|
204
|
+
can change an answer by design, so for each team and agent they start on
|
|
205
|
+
probation: the original request goes up alongside the optimized one, and the app
|
|
206
|
+
gets the optimized answer only when the two match. If they differ, the app gets
|
|
207
|
+
the original's answer (`x-token-lens-answer: original`) and the fix is switched
|
|
208
|
+
off as above. After `verify.probation` matches in a row (5 by default) the fix
|
|
209
|
+
runs on its own, still checked at least 25% of the time. During probation the
|
|
210
|
+
extra call is counted as check cost, and opt-in fixes are left out of streamed
|
|
211
|
+
and batched calls, whose answers can't be compared before the app gets them.
|
|
212
|
+
- Fixes that never change the request's content (prompt caching, batching on the
|
|
213
|
+
same model, replaying an identical retry) aren't switched off. Images are never
|
|
214
|
+
shrunk: providers already resize them to their own limit before counting tokens.
|
|
215
|
+
|
|
216
|
+
## Optimizing a running agent's whole run
|
|
217
|
+
|
|
218
|
+
Send the agent's model calls through the gateway (base URL) and give each run a
|
|
219
|
+
name with `x-token-lens-run` and the agent a name with `x-token-lens-agent`.
|
|
220
|
+
Every step of the run is then optimized as above, and the run is judged as a
|
|
221
|
+
whole, not call by call:
|
|
222
|
+
|
|
223
|
+
- **Total cost of the run**: `/token-lens/runs/<id>` adds up every step,
|
|
224
|
+
replayed retries (free) and output checks (shown separately): `costBeforeUsd`,
|
|
225
|
+
`costUsd`, `savedUsd`, `checkCostUsd`, `netSavedUsd`, and each step's own cost.
|
|
226
|
+
- **What the agent does across steps**: repeated tool calls (same tool, same
|
|
227
|
+
input), loops (the same call several times in a row), retries, output that
|
|
228
|
+
costs more than input, and context that keeps growing.
|
|
229
|
+
- **What happens about it**: the gateway fixes what it can without changing the
|
|
230
|
+
answer (identical tool results sent once, caching, retry replay, compact tool
|
|
231
|
+
output). What only the agent's code can change, such as skipping a repeated
|
|
232
|
+
step, caching tool results or adding a step limit, is named in the run's
|
|
233
|
+
findings with the tokens it costs. A run budget is there for runaway loops when
|
|
234
|
+
you turn it on.
|
|
235
|
+
|
|
236
|
+
### Test it: the same agent with and without Token Lens
|
|
237
|
+
|
|
238
|
+
`scripts/agent-benchmark.mjs` runs a multi-step customer-care agent (4 tools,
|
|
239
|
+
about 10 tool calls, refunds two delayed orders) through the gateway twice:
|
|
240
|
+
once observed only, with requests sent as written, and once optimized. It
|
|
241
|
+
compares the actions taken, the final answer, tokens and the run's cost,
|
|
242
|
+
including the output checks.
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
npm install && npm run build:gateway
|
|
246
|
+
npm run benchmark:agent # stand-in model, no key, runs locally and in CI
|
|
247
|
+
npm run benchmark:agent -- --api anthropic # the same stand-in behind the Anthropic API (simulates prompt caching)
|
|
248
|
+
npm run benchmark:agent -- --extra-tools 30 --allow lazy-tools # 30 more tools the agent doesn't need, with lazy-tools
|
|
249
|
+
ANTHROPIC_API_KEY=... npm run benchmark:agent -- --provider anthropic --model claude-opus-5-5
|
|
250
|
+
OPENAI_API_KEY=... npm run benchmark:agent -- --provider openai --model <your-model-id>
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
With a real provider `--model` is required. Pass the exact model your agent
|
|
254
|
+
runs (for example `claude-opus-5-5` or `claude-sonnet-5-5`, or the OpenAI model
|
|
255
|
+
id shown in your account), so the result is about that model, not a smaller one.
|
|
256
|
+
|
|
257
|
+
With a real model it also runs the baseline a second time as a control, so the
|
|
258
|
+
model's own run-to-run variation is visible next to any difference Token Lens
|
|
259
|
+
makes. `--out result.json` saves everything. Checks run on every optimized step
|
|
260
|
+
here (capped by `maxShareOfSavings`) to give the most evidence, so on a short run
|
|
261
|
+
their cost can exceed the saving; in production about 5% of calls are checked.
|
|
262
|
+
|
|
263
|
+
### Work that can wait: half price (`x-token-lens-batch`)
|
|
264
|
+
|
|
265
|
+
Overnight reports, evaluations and bulk summaries don't need an answer in
|
|
266
|
+
seconds. Send them with `x-token-lens-batch: true`:
|
|
267
|
+
|
|
268
|
+
- **What happens:** the gateway applies its usual fixes, then queues the call
|
|
269
|
+
for the provider's batch option on the same model. That is OpenAI's Batch API
|
|
270
|
+
or Anthropic's Message Batches. Both bill about half the normal price and
|
|
271
|
+
answer within 24 hours.
|
|
272
|
+
- **What the app gets back:** `202` and a job id straight away. Fetch the answer
|
|
273
|
+
later from `GET /token-lens/batches/<id>`. It returns `queued`, `submitted`,
|
|
274
|
+
`completed` with the provider's normal response body, or `failed` with the
|
|
275
|
+
reason. Each result is handed over once and kept for a day.
|
|
276
|
+
- **Reports:** batched calls show at the batch price next to what they would have
|
|
277
|
+
cost, and carry a `batch` action.
|
|
278
|
+
- **When it's refused:** streamed requests, and upstreams without a batch option.
|
|
279
|
+
In-house servers get `"batch": true` on their upstream if they speak OpenAI's
|
|
280
|
+
Batch API. A refused request returns an error instead of being sent at full
|
|
281
|
+
price.
|
|
282
|
+
- **Timing:** queued calls go out every `batch.flushSeconds` (60) and results are
|
|
283
|
+
checked every `batch.pollSeconds` (60). `batch.discount` (0.5) is the price
|
|
284
|
+
factor used in reports.
|
|
285
|
+
|
|
286
|
+
### Embeddings: unchanged documents once
|
|
287
|
+
|
|
288
|
+
Search and retrieval pipelines often re-embed every document on each sync,
|
|
289
|
+
changed or not. On `/v1/embeddings` the gateway keeps each vector it gets, keyed
|
|
290
|
+
by a hash of the input with the team, upstream, model, `dimensions` and
|
|
291
|
+
`encoding_format`, and sends only new inputs upstream. The response is the
|
|
292
|
+
provider's own vectors in the request's order, with
|
|
293
|
+
`x-token-lens-cached-embeddings` and `-saved-tokens` headers.
|
|
294
|
+
|
|
295
|
+
- Texts aren't stored, only their hash and vector, in this gateway's memory up to
|
|
296
|
+
`embeddingsCacheMB` (256), least recently used out first.
|
|
297
|
+
- Teams don't share entries. Turn it off with `"safe": { "cacheEmbeddings": false }`.
|
|
298
|
+
|
|
299
|
+
### Price tiers
|
|
300
|
+
|
|
301
|
+
Some models bill the whole request at a higher rate once its input passes a
|
|
302
|
+
size: Gemini Pro and Claude Sonnet's 1M-token window above 200K input tokens,
|
|
303
|
+
for example. Built-in models carry their list price for this, and `priceTiers`
|
|
304
|
+
in the config adds or overrides models by id. The gateway:
|
|
305
|
+
|
|
306
|
+
- prices calls past the size at the higher rate in every report, and prices the
|
|
307
|
+
"without Token Lens" cost at that rate when the original request was past it;
|
|
308
|
+
- adds `x-token-lens-price-tier` to calls within 10% of it or past it;
|
|
309
|
+
- reports a `price-tier` run finding with what to drop first.
|
|
310
|
+
|
|
311
|
+
Nothing is cut automatically, since that would change the answer.
|
|
312
|
+
|
|
313
|
+
### Images
|
|
314
|
+
|
|
315
|
+
The gateway reads each image's size and estimates its tokens with the
|
|
316
|
+
provider's published rules: about w×h/750 for Anthropic, and 512 px tiles for
|
|
317
|
+
OpenAI. Run reports show those tokens, and a repeated image is sent once
|
|
318
|
+
(`dedupe-images`).
|
|
319
|
+
|
|
320
|
+
Hosted providers shrink large images before counting them. Anthropic fits them
|
|
321
|
+
to 1,568 px; OpenAI fits them to 2,048 px with a 768 px short side. So sending a
|
|
322
|
+
smaller image than that saves upload time, not tokens, and the gateway doesn't
|
|
323
|
+
claim a saving for it. The changes that do save tokens are in the run report's
|
|
324
|
+
"images" finding: keep a description instead of an old screenshot, crop to the
|
|
325
|
+
part that matters, or use low detail where it is enough. Low detail can change
|
|
326
|
+
answers, so check it with output checks on.
|
|
327
|
+
|
|
328
|
+
### Large tool lists (`lazy-tools`)
|
|
329
|
+
|
|
330
|
+
Agents connected to large MCP servers often send dozens of tool definitions on
|
|
331
|
+
every call and use only a few. With `x-token-lens-allow: lazy-tools`, or
|
|
332
|
+
`"optIn": { "lazyTools": { "minTools": 8 } }` in the config:
|
|
333
|
+
|
|
334
|
+
- **What goes up:** tools the run hasn't used yet are sent as a name and a
|
|
335
|
+
one-line summary, plus a small `token_lens_load_tools` tool.
|
|
336
|
+
- **Loading on demand:** when the model calls the loader, or calls a summarized
|
|
337
|
+
tool directly, the gateway adds those tools' full definitions and sends the same
|
|
338
|
+
request again. The app only ever gets the final answer. Any tool call in it was
|
|
339
|
+
made with that tool's full definition, and the loader never reaches the app.
|
|
340
|
+
- **Later calls:** tools a run has loaded or used stay in full on its later calls,
|
|
341
|
+
so each tool is loaded at most once per run.
|
|
342
|
+
- **When it applies:** non-streamed requests with at least 8 plain function tools.
|
|
343
|
+
A tool forced with `tool_choice` is always sent in full.
|
|
344
|
+
- **Cost accounting:** the extra loading calls are counted in the run's cost.
|
|
345
|
+
The saving shown is net of them, and can be negative when a run uses most of
|
|
346
|
+
its tools.
|
|
347
|
+
|
|
348
|
+
It can change which tool the model picks, so it is opt-in, and output checks
|
|
349
|
+
sample it more often. In the stand-in benchmark with 34 tools, the run went from
|
|
350
|
+
94,741 to 48,452 tokens (49% fewer). The agent took the same actions and gave an
|
|
351
|
+
identical answer, and 5 of 5 output checks matched.
|
|
352
|
+
|
|
353
|
+
### What else the run report spots
|
|
354
|
+
|
|
355
|
+
These need a change in the agent, so the gateway shows them with the fix and
|
|
356
|
+
its saving, and changes nothing itself:
|
|
357
|
+
|
|
358
|
+
- **`cache-breaker`:** the system prompt or tool list starts the same as the
|
|
359
|
+
previous call's and then changes: a timestamp, a date, an id, a number, or
|
|
360
|
+
tools in a different order. The provider's cache misses from that point, so
|
|
361
|
+
those tokens are billed in full instead of at about a tenth. The report names
|
|
362
|
+
the kind of change, never the text, and gives the change to make (move it to the
|
|
363
|
+
newest message, or sort tools once).
|
|
364
|
+
- **`json-retries`:** the model's JSON reply didn't parse and the agent asked
|
|
365
|
+
again, each time a whole extra step. The fix is the provider's strict
|
|
366
|
+
structured output on the same model, written for the API in use (`json_schema`
|
|
367
|
+
response format on OpenAI, a forced tool on Anthropic).
|
|
368
|
+
- **`shared-reads`:** files or documents this run read that other runs of the
|
|
369
|
+
team (other sub-agents) read too, matched by a hash of the path. The fix loads
|
|
370
|
+
them once and passes them on, or puts them at the top of each agent's prompt so
|
|
371
|
+
the provider's cache serves them.
|
|
372
|
+
- **`price-tier`:** see [Price tiers](#price-tiers).
|
|
373
|
+
|
|
374
|
+
### Several gateway copies, and restarts: shared storage
|
|
375
|
+
|
|
376
|
+
By default reports live in memory and start empty after a restart. To share
|
|
377
|
+
them across copies behind a load balancer, and keep them across restarts, point
|
|
378
|
+
the gateway at your own Redis or Valkey:
|
|
379
|
+
|
|
380
|
+
```bash
|
|
381
|
+
TOKEN_LENS_REDIS_URL=rediss://:password@redis.internal:6380/0 npm run gateway
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
or `"storage": { "redisUrl": "env:REDIS_URL" }` in the config. A single gateway
|
|
385
|
+
can use a file instead (`"storage": { "file": "/var/lib/token-lens/reports.json" }`
|
|
386
|
+
or `TOKEN_LENS_STORAGE_FILE`).
|
|
387
|
+
|
|
388
|
+
- **Totals:** every copy adds its own changes to one set of counters every
|
|
389
|
+
`storage.flushSeconds` (10), so the report, CSV and stats show all copies.
|
|
390
|
+
- **Run reports:** kept `storage.runDays` (7) days. Any copy can show any run.
|
|
391
|
+
- **Switched-off fixes:** shared, so a fix that changed an answer on one copy is
|
|
392
|
+
off on all of them, and an admin reset turns it back on everywhere.
|
|
393
|
+
- **What is stored:** only what reports already show: totals, run summaries,
|
|
394
|
+
findings and per-call counts, with tool inputs hashed. Never prompts or
|
|
395
|
+
answers. Keys start with `storage.keyPrefix` (`token-lens:`).
|
|
396
|
+
|
|
397
|
+
### Savings export for finance
|
|
398
|
+
|
|
399
|
+
`GET /token-lens/report.csv` downloads tokens and money before and after, output
|
|
400
|
+
check cost and net saving, one row per team, agent and model.
|
|
401
|
+
`/token-lens/report.csv?by=run` gives one row per recent run. The HTML report
|
|
402
|
+
links both.
|
|
403
|
+
|
|
404
|
+
## Where the tokens go: run reports
|
|
405
|
+
|
|
406
|
+
`GET /token-lens/runs/<run id>` explains a run: its calls, the parts of each, and
|
|
407
|
+
findings ranked by how much they would save. Each finding says what the gateway
|
|
408
|
+
already does about it, what a run like this one would save in tokens and
|
|
409
|
+
dollars, and the exact changes that cut it, each with where it goes (gateway
|
|
410
|
+
config, request header, agent code, system prompt, model server) and what to
|
|
411
|
+
paste. None of them switch to a different or smaller model. For example:
|
|
412
|
+
|
|
413
|
+
```
|
|
414
|
+
- The agent calls the same tool with the same input more than once - saves ~3,377 tokens ($0.0068) per run
|
|
415
|
+
agent code: Cache tool results for the run, so get_order with the same input runs once
|
|
416
|
+
const cache = new Map(); const run = (name, args) => { ... };
|
|
417
|
+
system prompt: Tell the agent results don't change within a run
|
|
418
|
+
Tool results stay valid for this whole task. Do not call a tool again with the same input; use the earlier result.
|
|
419
|
+
- Tool outputs take a large share of every call
|
|
420
|
+
request header: Replace older tool outputs with a stub (opt-in; output checks run on a sample)
|
|
421
|
+
x-token-lens-allow: elide-tool-outputs
|
|
422
|
+
agent code: Return only the fields the agent uses from each tool
|
|
423
|
+
return JSON.stringify(pick(result, ["id", "status", "total"]));
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
The HTML report (`/token-lens/report`) shows the same under "What to change
|
|
427
|
+
next" for each recent run.
|
|
428
|
+
|
|
429
|
+
Findings cover repeated prefixes, growing history, tool outputs, large
|
|
430
|
+
retrieved context, retries, repeated tool calls, loops, output-heavy runs, hidden
|
|
431
|
+
reasoning that outweighs the answer, and runs nearing the context limit.
|
|
432
|
+
|
|
433
|
+
- `GET /token-lens/runs` lists recent runs (`?app=` to filter).
|
|
434
|
+
- `GET /token-lens/stats` has the report's totals plus tokenizer calibration.
|
|
435
|
+
- `GET /metrics` exposes the same totals to Prometheus, labelled by team, agent
|
|
436
|
+
and model.
|
|
437
|
+
- `POST /token-lens/analyze` with `{"prompt", "model"}` returns the Token Lens
|
|
438
|
+
analysis of one prompt without calling a model.
|
|
439
|
+
|
|
440
|
+
## Security
|
|
441
|
+
|
|
442
|
+
- Set client keys (`TOKEN_LENS_CLIENT_KEYS="name:key,…"` or `clientKeys` in the
|
|
443
|
+
config). Each key's name becomes the `app` in logs, stats and metrics. Without
|
|
444
|
+
keys, anyone who can reach the port can use the upstream keys; the gateway
|
|
445
|
+
warns about this at startup.
|
|
446
|
+
- Upstream keys are read from the environment (`"env:NAME"` in the config) and
|
|
447
|
+
never logged. When an upstream has no key, the caller's own key is passed
|
|
448
|
+
through.
|
|
449
|
+
- Logs are one JSON line per call with counts, model, status and the fixes
|
|
450
|
+
applied; never prompt or response text. Run reports hold counts only. Tool
|
|
451
|
+
inputs, which can hold customer data, show only as a short hash (for example
|
|
452
|
+
`get_order(input #3f2a9c1e)`) unless you set `"reportToolArgs": true`.
|
|
453
|
+
- `/token-lens/*` and `/metrics` need a client key (when keys are set); model
|
|
454
|
+
registration needs the admin key.
|
|
455
|
+
|
|
456
|
+
## Privacy: nothing leaves your systems
|
|
457
|
+
|
|
458
|
+
The gateway runs entirely on your own servers and needs no account, licence
|
|
459
|
+
server or Token Lens service.
|
|
460
|
+
|
|
461
|
+
- **Outbound requests:** it makes requests only to the upstreams you configure.
|
|
462
|
+
There is no telemetry, no update check and no call home.
|
|
463
|
+
- **Reports and metrics:** stats, run reports, the HTML report and `/metrics` are
|
|
464
|
+
kept in memory on your gateway (or in your own Redis or file, if you turn on
|
|
465
|
+
shared storage), and only your own clients can read them.
|
|
466
|
+
- **What providers see:** the provider receives the same request your app sent,
|
|
467
|
+
minus the `x-token-lens-*` headers. OpenAI's prompt cache key is a hash, so app
|
|
468
|
+
and run names never reach it.
|
|
469
|
+
- **Output checks:** these re-send a sample of requests to the same upstream your
|
|
470
|
+
app already uses. Set `"verify": { "sampleRate": 0 }` to turn them off.
|
|
471
|
+
- **Embeddings and file reads:** cached embeddings are kept as a hash of the text
|
|
472
|
+
and its vector, in memory, per team. Files several agents read are matched by a
|
|
473
|
+
hash of the path; reports show only how many and which agents.
|
|
474
|
+
- **Test coverage:** a test checks all of this on every change. It records every
|
|
475
|
+
outbound request and asserts that customer data never appears in reports or
|
|
476
|
+
logs.
|
|
477
|
+
|
|
478
|
+
## Limits
|
|
479
|
+
|
|
480
|
+
- Without shared storage, run reports and stats are kept in memory per gateway
|
|
481
|
+
copy (the most recent 2,000 runs). With it, totals are combined exactly; a run
|
|
482
|
+
served by two copies at once is reported by whichever wrote last, so route a run
|
|
483
|
+
to one copy (by the `x-token-lens-run` header) where that matters. `/metrics`
|
|
484
|
+
is always per copy.
|
|
485
|
+
- Translation between the two APIs covers Anthropic Messages and OpenAI Chat
|
|
486
|
+
Completions. The OpenAI Responses API reaches only OpenAI-compatible upstreams.
|
|
487
|
+
- Counts for models without a known tokenizer are estimates until the upstream
|
|
488
|
+
has reported usage a few times.
|
|
489
|
+
- Switched-off fixes and probation counts are kept in memory per gateway
|
|
490
|
+
instance, so a restart puts opt-in fixes back on probation.
|
|
491
|
+
- A streamed answer longer than 16 MB isn't checked.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kunal Dutt
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/PRIVACY.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Token Lens privacy policy
|
|
2
|
+
|
|
3
|
+
_Last updated: 13 October 2026_
|
|
4
|
+
|
|
5
|
+
Token Lens collects no data. Not from individuals, not from companies, not from
|
|
6
|
+
anyone.
|
|
7
|
+
|
|
8
|
+
- **No accounts, analytics, telemetry, crash reports, ads or trackers.** Token
|
|
9
|
+
Lens never "phones home". There is no Token Lens server that receives your
|
|
10
|
+
usage, and the publisher cannot see who uses it or how.
|
|
11
|
+
- **Your prompts and chats never leave your device.** Every count, rewrite and
|
|
12
|
+
tip is computed on your own computer, browser or servers.
|
|
13
|
+
- **Nothing is sold or shared,** because nothing is collected.
|
|
14
|
+
|
|
15
|
+
## Each way of running Token Lens
|
|
16
|
+
|
|
17
|
+
| Where it runs | What it reads | What it keeps | What it sends |
|
|
18
|
+
| --- | --- | --- | --- |
|
|
19
|
+
| **Browser extension** (Chrome, Edge, Firefox) | The prompt box, model picker and visible messages on the AI chat sites listed in its permissions, to count tokens. | Your settings, prices, budget and savings totals, in your browser's own extension storage. Uninstalling deletes them. | Nothing. Its only network request is on claude.ai: at most once a minute it reads your own session and weekly usage from claude.ai, signed in as you, so plan tips use real numbers. The result is not stored and goes nowhere else. |
|
|
20
|
+
| **MCP server on your computer** (`npx @kunal_dutt/token-lens`, Claude Desktop extension) | Only the text an AI app passes to its tools. | Nothing. | Nothing by default. Only if you set your own `ANTHROPIC_API_KEY` or `GEMINI_API_KEY` for it does it send the text to that provider's token-counting endpoint, with your key, for an exact count. |
|
|
21
|
+
| **Your own connector** (`npx @kunal_dutt/token-lens --http`) | Only the text sent to its tools. | Nothing. | Nothing beyond the reply. |
|
|
22
|
+
| **Public hosted connector** (`/api/mcp`) | Only the text an AI app passes to its tools. | Nothing. Each request is handled in memory and discarded; requests are not logged or stored. The hosting provider keeps standard request metadata (time, IP address, URL) for security, as any website does. | Nothing beyond the reply. Prefer running your own copy if even that metadata matters. |
|
|
23
|
+
| **Developer library** (`@kunal_dutt/token-lens/sdk` on npm, `token-lens` on PyPI, inside your app) | Your app's requests to OpenAI or Anthropic, in your own process. | Savings counters in your app's memory. | Nothing of its own. Your requests still go only to the provider your code already calls. |
|
|
24
|
+
| **Gateway** (Docker image or `npx @kunal_dutt/token-lens gateway`, on your servers) | Your apps' model requests, which you route through it. | Savings counters in memory on your server. | Only to the model endpoints *you* configure (your OpenAI, Anthropic, Azure or in-house model). Nothing goes to Token Lens or anyone else. |
|
|
25
|
+
|
|
26
|
+
## Stores and registries
|
|
27
|
+
|
|
28
|
+
Chrome Web Store, Microsoft Edge Add-ons, Firefox Add-ons, npm, PyPI, GitHub
|
|
29
|
+
and the MCP Registry may record installs or downloads under their own privacy policies.
|
|
30
|
+
Token Lens receives none of that.
|
|
31
|
+
|
|
32
|
+
## Changes and contact
|
|
33
|
+
|
|
34
|
+
If this ever changed, the policy would be updated here first and the change
|
|
35
|
+
would ship as a new version, never silently. Questions: contact
|
|
36
|
+
the publisher, Kunal Dutt, through the support link on any store listing.
|
package/README.md
CHANGED
|
@@ -1,3 +1,81 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Token Lens
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Count and cut the tokens your prompts, chats and agents use, adapted to the
|
|
4
|
+
model and the task, without switching your model or changing the answer. Every
|
|
5
|
+
saving is shown in tokens and dollars, priced for the model you use.
|
|
6
|
+
|
|
7
|
+
**Collects no data.** No accounts, analytics or telemetry. It makes no network
|
|
8
|
+
requests of its own, except an exact token count from Anthropic or Google when
|
|
9
|
+
you set your own `ANTHROPIC_API_KEY` or `GEMINI_API_KEY` for it. See [PRIVACY.md](PRIVACY.md).
|
|
10
|
+
|
|
11
|
+
## Add it to your AI app
|
|
12
|
+
|
|
13
|
+
It runs on your own computer with Node 20 or newer. `@latest` means
|
|
14
|
+
each start picks up the newest version, so there's nothing to update.
|
|
15
|
+
|
|
16
|
+
| App | How |
|
|
17
|
+
| --- | --- |
|
|
18
|
+
| **Claude Code** | `claude mcp add token-lens -- npx -y @kunal_dutt/token-lens@latest` |
|
|
19
|
+
| **Claude Desktop** | Add the JSON below to `claude_desktop_config.json` (keeps itself up to date), or for a one-click install without Node, download [token-lens.mcpb](https://unpkg.com/@kunal_dutt/token-lens@latest/token-lens.mcpb) and open it. |
|
|
20
|
+
| **Cursor, VS Code, Windsurf, Zed, any MCP app** | Add a stdio server with the JSON below. |
|
|
21
|
+
| **ChatGPT, claude.ai and mobile apps** | Need a connector URL: use the public one from the Token Lens listing, or run your own (below). |
|
|
22
|
+
|
|
23
|
+
```json
|
|
24
|
+
{
|
|
25
|
+
"mcpServers": {
|
|
26
|
+
"token-lens": { "command": "npx", "args": ["-y", "@kunal_dutt/token-lens@latest"] }
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Then ask, for example: "optimize this prompt with Token Lens", "how full is this
|
|
32
|
+
chat?" or "optimize this agent's system prompt and tools".
|
|
33
|
+
|
|
34
|
+
Tools: `optimize_prompt`, `optimize_agent`, `count_tokens`,
|
|
35
|
+
`check_conversation`, `list_model_profiles`.
|
|
36
|
+
|
|
37
|
+
## Run your own connector
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npx -y @kunal_dutt/token-lens@latest --http 8787 # serves http://localhost:8787/mcp
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Put it behind HTTPS (any reverse proxy or tunnel) and add `https://<host>/mcp`
|
|
44
|
+
as a custom connector in ChatGPT or claude.ai. It stores nothing.
|
|
45
|
+
|
|
46
|
+
## Cost check for agent builders
|
|
47
|
+
|
|
48
|
+
Every pull request that changes an agent's prompts or tool definitions gets a
|
|
49
|
+
comment with the token and dollar change per call (and per month), priced for
|
|
50
|
+
the model the agent runs on, plus what could be trimmed with the same output.
|
|
51
|
+
|
|
52
|
+
- **GitHub:** copy [agent-cost-check.yml](https://unpkg.com/@kunal_dutt/token-lens@latest/agent-cost-check.yml)
|
|
53
|
+
to `.github/workflows/`, then set `--model` and your agent file paths.
|
|
54
|
+
- **Any other CI or locally:**
|
|
55
|
+
`npx -y @kunal_dutt/token-lens@latest check-agent --model claude-opus-5-5 --base origin/main 'agents/**'`
|
|
56
|
+
prints the same report as Markdown (`--out file.md` to save it,
|
|
57
|
+
`--fail-above 500` to fail when a change adds more than 500 tokens per call).
|
|
58
|
+
|
|
59
|
+
Agent files are `.json` specs (`system`, `tools`, `examples`, `messages`,
|
|
60
|
+
`prompt`; OpenAI- and Anthropic-style tool lists work as-is) or any text file,
|
|
61
|
+
read as a system prompt. It runs inside your CI and sends nothing anywhere.
|
|
62
|
+
|
|
63
|
+
## Gateway for companies
|
|
64
|
+
|
|
65
|
+
A self-hosted proxy that speaks the OpenAI and Anthropic APIs and optimizes
|
|
66
|
+
every call your apps, workflows and agents make, with any model, including your
|
|
67
|
+
own. It talks only to the model endpoints you configure. See
|
|
68
|
+
[GATEWAY.md](GATEWAY.md).
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
docker run -p 8080:8080 -e OPENAI_API_KEY ghcr.io/duttkunal101-lab/token-lens-gateway:latest
|
|
72
|
+
# or
|
|
73
|
+
OPENAI_API_KEY=... npx -y @kunal_dutt/token-lens@latest gateway
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Browser extension
|
|
77
|
+
|
|
78
|
+
For ChatGPT, Claude, Gemini, DeepSeek, Le Chat and Grok on the web: search
|
|
79
|
+
"Token Lens" in the Chrome Web Store, Microsoft Edge Add-ons or Firefox Add-ons.
|
|
80
|
+
|
|
81
|
+
MIT licensed.
|