slash-tokens 0.2.0 → 1.0.0

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
@@ -1,128 +1,26 @@
1
1
  # /slash-tokens
2
2
 
3
- Token budget estimation in 4.8 KB WASM. Sub-millisecond. Zero dependencies.
3
+ Pre-flight checks for API calls. 4.8 KB WASM. Sub-millisecond. Zero dependencies.
4
4
 
5
- I don't count tokens. I slash them.
6
-
7
- ## Install
5
+ **Fly First. Pay Economy.**
8
6
 
9
7
  ```bash
10
8
  npm install slash-tokens
11
9
  ```
12
10
 
13
- ## Use
14
-
15
11
  ```js
16
- import { slash } from 'slash-tokens'
17
-
18
- const tokens = slash('Your content here')
19
-
20
- if (tokens > 128_000) trim(content) // Context window gate
21
- const cost = tokens * PRICE_PER_TOKEN // Cost estimation
22
- ```
12
+ import { preflight } from 'slash-tokens'
23
13
 
24
- One function. Returns a number. That's the entire API.
25
-
26
- ## Scan your project
27
-
28
- ```bash
29
- bunx slash-tokens
14
+ const check = preflight(prompt, 'claude-opus')
15
+ // tokens: 47,000 | cost: $0.71 | 11 cheaper options | save 99%
30
16
  ```
31
17
 
32
- Finds every AI API call in your codebase and estimates your token burn:
33
-
34
- ```
35
- ⚡ /slash
36
- Token burn analysis
37
-
38
- Scanned 47 files in 3ms
39
-
40
- CALL SITES
41
- ────────────────────────────────────────────────────────
42
- src/api/chat.ts:24 Anthropic ~2,400 tok/call
43
- src/api/summary.ts:11 OpenAI ~8,200 tok/call
44
- src/lib/agent.ts:88 Vercel AI ~1,100 tok/call
45
-
46
- DAILY BURN (100 calls/site/day)
47
- ────────────────────────────────────────────────────────
48
- Input tokens: 1,520,000
49
- Output tokens: 3,040,000 (estimated)
50
- Total: 4,560,000 tokens/day
51
-
52
- MONTHLY COST ($3/$15 per MTok)
53
- ────────────────────────────────────────────────────────
54
- Total: $1,504.80/mo
55
-
56
- ⚡ SLASH SAVINGS (10% gate efficiency)
57
- ────────────────────────────────────────────────────────
58
- Annual savings: $1,805.76/yr
59
- ```
60
-
61
- Detects: OpenAI, Anthropic, Vercel AI, LangChain, Gemini, Bedrock, Grok, Cohere, Mistral.
62
-
63
- ## How it works
64
-
65
- Single-pass byte classification over your content. No vocabulary tables. No BPE decode. Classifies content type (prose, code, JSON, YAML, mixed), applies a calibrated chars-per-token ratio, adds a safety margin.
66
-
67
- Budget gate — better to say "doesn't fit" than overflow. The API returns exact counts after the call. Pre-call, you only need go/no-go.
68
-
69
- ## Numbers
70
-
71
- | | js-tiktoken | gpt-tokenizer | slash-tokens |
72
- |---|---|---|---|
73
- | Size | ~1 MB | 300 KB+ | **4.8 KB** |
74
- | Cold start | Heavy | Moderate | **Sub-ms** |
75
- | Edge-ready | Painful | Difficult | **Native** |
76
- | Models | GPT only | GPT only | **Any model** |
77
- | Dependencies | npm tree | npm tree | **Zero** |
78
- | Allocations | Many | Many | **Zero** |
79
-
80
- ## Runtime support
81
-
82
- Works everywhere WASM runs:
83
-
84
- - Node.js
85
- - Bun
86
- - Deno
87
- - Cloudflare Workers
88
- - Vercel Edge
89
- - Browser
90
-
91
- ## API
92
-
93
- ### `slash(content: string): number`
94
-
95
- Estimate token count for a string. Returns `u32`.
18
+ Or one line — every API call pre-flighted automatically:
96
19
 
97
20
  ```js
98
- slash('') // 0
99
- slash('hello') // 1
100
- slash(longDocument) // 4,283
21
+ import 'slash-tokens/auto'
101
22
  ```
102
23
 
103
- ### `slashBytes(bytes: Uint8Array): number`
104
-
105
- Estimate token count from raw bytes. Skips `TextEncoder` overhead.
106
-
107
- ## What this is not
108
-
109
- This is not a tokenizer. It doesn't decode BPE. It doesn't reproduce exact token counts. No tokenizer can — Claude, Grok, and Gemini vocabularies are not public.
110
-
111
- This is a **budget gate**. It answers "does this fit?" with 96-98% accuracy in sub-millisecond time. The 2-4% margin is intentional — overestimate, never underestimate.
112
-
113
- ## Engine
114
-
115
- 4,865 bytes of Zig-compiled WASM (`wasm32-freestanding`). No allocator. No floats. No heap. Base64-embedded — no separate `.wasm` file to load.
116
-
117
- Built from [xai-faf-zig](https://github.com/Wolfe-Jam/xai-faf-zig). 172 tests including 65 adversarial (CJK, emoji, binary, base64, threshold boundaries).
118
-
119
- ## Links
120
-
121
- - [slashtokens.com](https://slashtokens.com) — product site
122
- - [mcpaas.live/slash](https://mcpaas.live/slash) — live one-pager
123
- - [npm](https://www.npmjs.com/package/slash-tokens)
124
- - [xai-faf-zig](https://github.com/Wolfe-Jam/xai-faf-zig) — WASM engine source
125
-
126
- ## License
24
+ Full docs, examples, and model pricing at **[GitHub](https://github.com/Wolfe-Jam/slash-tokens)**
127
25
 
128
- MIT
26
+ [slashtokens.com](https://slashtokens.com) | MIT
package/dist/auto.d.ts ADDED
@@ -0,0 +1 @@
1
+ export {};
package/dist/auto.js ADDED
@@ -0,0 +1,30 @@
1
+ import { patchFetch, onIntercept } from './intercept.js';
2
+ import { report } from './transact.js';
3
+ import { hasKey } from './config.js';
4
+ // Auto mode: import this file and every LLM call is pre-flighted.
5
+ // Usage: import 'slash-tokens/auto'
6
+ //
7
+ // No key: console logging only (free forever)
8
+ // With key (init or SLASH_KEY): flight records sent to API
9
+ //
10
+ // Auto mode observes every call — it doesn't block.
11
+ // The data shows what you're spending and what you COULD save.
12
+ // When you start acting on preflight results (skip/reduce/route),
13
+ // the savings become real.
14
+ patchFetch();
15
+ onIntercept((event) => {
16
+ const status = event.fits ? 'OK' : 'OVER LIMIT';
17
+ console.log(`[slash] ${event.provider} ${event.model} | ${event.tokens.toLocaleString()} tokens | $${event.cost.toFixed(4)} | ${status}`);
18
+ // If key is configured, log the flight record (non-blocking)
19
+ if (hasKey() && event.tokens > 0) {
20
+ report({
21
+ tokens_estimated: event.tokens,
22
+ tokens_saved: 0,
23
+ model: event.model,
24
+ action: 'routed',
25
+ cost_saved_usd: 0,
26
+ }).catch(() => {
27
+ // Silent — never break the app for billing failures
28
+ });
29
+ }
30
+ });
package/dist/config.d.ts CHANGED
@@ -4,3 +4,4 @@ export declare function init(opts: {
4
4
  }): void;
5
5
  export declare function resolveKey(perCallKey?: string): string;
6
6
  export declare function getEndpoint(): string;
7
+ export declare function hasKey(): boolean;
package/dist/config.js CHANGED
@@ -16,3 +16,6 @@ export function resolveKey(perCallKey) {
16
16
  export function getEndpoint() {
17
17
  return _endpoint;
18
18
  }
19
+ export function hasKey() {
20
+ return !!(_key || process.env.SLASH_KEY);
21
+ }
package/dist/index.d.ts CHANGED
@@ -1,5 +1,9 @@
1
1
  export { slash, slashBytes } from './slash.js';
2
+ export { preflight } from './preflight.js';
3
+ export type { PreflightResult, Alternative } from './preflight.js';
4
+ export { MODELS, listModels } from './models.js';
5
+ export type { ModelInfo } from './models.js';
2
6
  export { report } from './transact.js';
3
7
  export type { ReportOptions, ReportResult } from './transact.js';
4
- export { init } from './config.js';
8
+ export { init, hasKey } from './config.js';
5
9
  export { resolveKey } from './config.js';
package/dist/index.js CHANGED
@@ -1,7 +1,11 @@
1
1
  // Core estimation
2
2
  export { slash, slashBytes } from './slash.js';
3
+ // Pre-flight checks
4
+ export { preflight } from './preflight.js';
5
+ // Model intelligence
6
+ export { MODELS, listModels } from './models.js';
3
7
  // Transaction reporting
4
8
  export { report } from './transact.js';
5
9
  // Configuration
6
- export { init } from './config.js';
10
+ export { init, hasKey } from './config.js';
7
11
  export { resolveKey } from './config.js';
@@ -0,0 +1,11 @@
1
+ export interface InterceptEvent {
2
+ endpoint: string;
3
+ provider: string;
4
+ model: string;
5
+ tokens: number;
6
+ cost: number;
7
+ fits: boolean;
8
+ timestamp: string;
9
+ }
10
+ export declare function onIntercept(handler: (event: InterceptEvent) => void): void;
11
+ export declare function patchFetch(): void;
@@ -0,0 +1,120 @@
1
+ import { slash } from './slash.js';
2
+ import { getModel } from './models.js';
3
+ // AI API endpoint detection — same patterns as scanner, runtime version
4
+ const AI_ENDPOINTS = [
5
+ {
6
+ pattern: /api\.anthropic\.com/,
7
+ provider: 'Anthropic',
8
+ modelExtractor: (body) => body?.model || 'claude-sonnet',
9
+ },
10
+ {
11
+ pattern: /api\.openai\.com/,
12
+ provider: 'OpenAI',
13
+ modelExtractor: (body) => body?.model || 'gpt-4o',
14
+ },
15
+ {
16
+ pattern: /generativelanguage\.googleapis\.com/,
17
+ provider: 'Google',
18
+ modelExtractor: (body) => 'gemini-2.0-flash',
19
+ },
20
+ {
21
+ pattern: /api\.x\.ai/,
22
+ provider: 'xAI',
23
+ modelExtractor: (body) => body?.model || 'grok-3',
24
+ },
25
+ ];
26
+ // Normalize model names to our pricing table keys
27
+ function normalizeModel(raw) {
28
+ const lower = raw.toLowerCase();
29
+ if (lower.includes('opus'))
30
+ return 'claude-opus';
31
+ if (lower.includes('sonnet'))
32
+ return 'claude-sonnet';
33
+ if (lower.includes('haiku'))
34
+ return 'claude-haiku';
35
+ if (lower.includes('grok') && lower.includes('mini'))
36
+ return 'grok-3-mini';
37
+ if (lower.includes('grok'))
38
+ return 'grok-3';
39
+ if (lower.includes('gemini') && lower.includes('pro'))
40
+ return 'gemini-2.0-pro';
41
+ if (lower.includes('gemini'))
42
+ return 'gemini-2.0-flash';
43
+ if (lower.includes('4o-mini') || lower.includes('4o_mini'))
44
+ return 'gpt-4o-mini';
45
+ if (lower.includes('4o'))
46
+ return 'gpt-4o';
47
+ if (lower.includes('4.1') && lower.includes('nano'))
48
+ return 'gpt-4.1-nano';
49
+ if (lower.includes('4.1') && lower.includes('mini'))
50
+ return 'gpt-4.1-mini';
51
+ if (lower.includes('4.1'))
52
+ return 'gpt-4.1';
53
+ return raw;
54
+ }
55
+ // Extract message content from request body
56
+ function extractContent(body) {
57
+ if (!body)
58
+ return '';
59
+ // Anthropic: { messages: [{ content: "..." }] }
60
+ // OpenAI: { messages: [{ content: "..." }] }
61
+ if (body.messages && Array.isArray(body.messages)) {
62
+ return body.messages
63
+ .map((m) => typeof m.content === 'string' ? m.content : JSON.stringify(m.content))
64
+ .join('\n');
65
+ }
66
+ // Anthropic: { prompt: "..." }
67
+ if (typeof body.prompt === 'string')
68
+ return body.prompt;
69
+ // Google: { contents: [{ parts: [{ text: "..." }] }] }
70
+ if (body.contents && Array.isArray(body.contents)) {
71
+ return body.contents
72
+ .flatMap((c) => c.parts || [])
73
+ .map((p) => p.text || '')
74
+ .join('\n');
75
+ }
76
+ return JSON.stringify(body);
77
+ }
78
+ let _onIntercept = null;
79
+ export function onIntercept(handler) {
80
+ _onIntercept = handler;
81
+ }
82
+ export function patchFetch() {
83
+ const originalFetch = globalThis.fetch;
84
+ globalThis.fetch = async function slashFetch(input, init) {
85
+ const url = typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url;
86
+ // Check if this is an AI API call
87
+ const match = AI_ENDPOINTS.find(ep => ep.pattern.test(url));
88
+ if (match && init?.body) {
89
+ try {
90
+ const bodyStr = typeof init.body === 'string' ? init.body : undefined;
91
+ if (bodyStr) {
92
+ const body = JSON.parse(bodyStr);
93
+ const content = extractContent(body);
94
+ const rawModel = match.modelExtractor(body);
95
+ const model = normalizeModel(rawModel);
96
+ const tokens = slash(content);
97
+ const info = getModel(model);
98
+ const cost = info ? Math.round(((tokens / 1000000) * info.input) * 1000000) / 1000000 : 0;
99
+ const fits = info ? tokens <= info.context : true;
100
+ const event = {
101
+ endpoint: url,
102
+ provider: match.provider,
103
+ model,
104
+ tokens,
105
+ cost,
106
+ fits,
107
+ timestamp: new Date().toISOString(),
108
+ };
109
+ if (_onIntercept) {
110
+ _onIntercept(event);
111
+ }
112
+ }
113
+ }
114
+ catch {
115
+ // Non-blocking — never break the actual API call
116
+ }
117
+ }
118
+ return originalFetch(input, init);
119
+ };
120
+ }
@@ -0,0 +1,8 @@
1
+ export interface ModelInfo {
2
+ input: number;
3
+ output: number;
4
+ context: number;
5
+ }
6
+ export declare const MODELS: Record<string, ModelInfo>;
7
+ export declare function getModel(name: string): ModelInfo | undefined;
8
+ export declare function listModels(): string[];
package/dist/models.js ADDED
@@ -0,0 +1,24 @@
1
+ export const MODELS = {
2
+ // Anthropic
3
+ 'claude-opus': { input: 15.00, output: 75.00, context: 200000 },
4
+ 'claude-sonnet': { input: 3.00, output: 15.00, context: 200000 },
5
+ 'claude-haiku': { input: 0.25, output: 1.25, context: 200000 },
6
+ // xAI
7
+ 'grok-3': { input: 5.00, output: 15.00, context: 131072 },
8
+ 'grok-3-mini': { input: 0.30, output: 0.50, context: 131072 },
9
+ // Google
10
+ 'gemini-2.0-flash': { input: 0.10, output: 0.40, context: 1048576 },
11
+ 'gemini-2.0-pro': { input: 1.25, output: 5.00, context: 1048576 },
12
+ // OpenAI
13
+ 'gpt-4o': { input: 2.50, output: 10.00, context: 128000 },
14
+ 'gpt-4o-mini': { input: 0.15, output: 0.60, context: 128000 },
15
+ 'gpt-4.1': { input: 2.00, output: 8.00, context: 1047576 },
16
+ 'gpt-4.1-mini': { input: 0.40, output: 1.60, context: 1047576 },
17
+ 'gpt-4.1-nano': { input: 0.10, output: 0.40, context: 1047576 },
18
+ };
19
+ export function getModel(name) {
20
+ return MODELS[name] || MODELS[name.toLowerCase()];
21
+ }
22
+ export function listModels() {
23
+ return Object.keys(MODELS);
24
+ }
@@ -0,0 +1,16 @@
1
+ export interface Alternative {
2
+ model: string;
3
+ cost: number;
4
+ savings: number;
5
+ savingsPercent: number;
6
+ }
7
+ export interface PreflightResult {
8
+ tokens: number;
9
+ cost: number;
10
+ fits: boolean;
11
+ model: string;
12
+ context: number;
13
+ utilization: number;
14
+ options: Alternative[];
15
+ }
16
+ export declare function preflight(content: string, model: string): PreflightResult;
@@ -0,0 +1,27 @@
1
+ import { slash } from './slash.js';
2
+ import { getModel, MODELS } from './models.js';
3
+ export function preflight(content, model) {
4
+ const tokens = slash(content);
5
+ const info = getModel(model);
6
+ if (!info) {
7
+ throw new Error(`Unknown model: "${model}". Available: ${Object.keys(MODELS).join(', ')}`);
8
+ }
9
+ const cost = Math.round(((tokens / 1000000) * info.input) * 1000000) / 1000000;
10
+ const fits = tokens <= info.context;
11
+ const utilization = Math.round((tokens / info.context) * 10000) / 10000;
12
+ const options = Object.entries(MODELS)
13
+ .filter(([m]) => m !== model)
14
+ .filter(([_, v]) => v.context >= tokens)
15
+ .map(([m, v]) => {
16
+ const altCost = Math.round(((tokens / 1000000) * v.input) * 1000000) / 1000000;
17
+ return {
18
+ model: m,
19
+ cost: altCost,
20
+ savings: Math.round((cost - altCost) * 1000000) / 1000000,
21
+ savingsPercent: cost > 0 ? Math.round(((cost - altCost) / cost) * 10000) / 100 : 0,
22
+ };
23
+ })
24
+ .filter(o => o.savings > 0)
25
+ .sort((a, b) => a.cost - b.cost);
26
+ return { tokens, cost, fits, model, context: info.context, utilization, options };
27
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "slash-tokens",
3
- "version": "0.2.0",
4
- "description": "Token budget estimation in 4.8 KB WASM. Sub-millisecond. Zero dependencies. I don't count tokens. I slash them.",
3
+ "version": "1.0.0",
4
+ "description": "Pre-flight checks for API calls. 4.8 KB WASM. Sub-millisecond. Zero dependencies. Fly First. Pay Economy.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
@@ -13,6 +13,10 @@
13
13
  ".": {
14
14
  "import": "./dist/index.js",
15
15
  "types": "./dist/index.d.ts"
16
+ },
17
+ "./auto": {
18
+ "import": "./dist/auto.js",
19
+ "types": "./dist/auto.d.ts"
16
20
  }
17
21
  },
18
22
  "files": ["dist"],