slash-tokens 1.5.1 → 1.5.3
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/dist/cli.js +13 -2
- package/dist/slash.js +33 -31
- package/dist/wasm.d.ts +16 -0
- package/dist/wasm.js +36 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -28,10 +28,21 @@ function getInstance() {
|
|
|
28
28
|
_instance = new WebAssembly.Instance(module);
|
|
29
29
|
return _instance;
|
|
30
30
|
}
|
|
31
|
+
var MAX_WASM_INPUT_BYTES = 32 * 1024 * 1024;
|
|
32
|
+
var WASM_PAGE_BYTES = 65536;
|
|
33
|
+
function ensureCapacity(memory, neededBytes) {
|
|
34
|
+
const cappedNeeded = Math.min(neededBytes, MAX_WASM_INPUT_BYTES);
|
|
35
|
+
if (memory.buffer.byteLength >= cappedNeeded)
|
|
36
|
+
return;
|
|
37
|
+
const additionalPages = Math.ceil((cappedNeeded - memory.buffer.byteLength) / WASM_PAGE_BYTES);
|
|
38
|
+
if (additionalPages > 0)
|
|
39
|
+
memory.grow(additionalPages);
|
|
40
|
+
}
|
|
31
41
|
function writeToMemory(content) {
|
|
32
42
|
const instance = getInstance();
|
|
33
43
|
const bytes = encoder.encode(content);
|
|
34
44
|
const memory = instance.exports.memory;
|
|
45
|
+
ensureCapacity(memory, WASM_INPUT_OFFSET + bytes.length + 1);
|
|
35
46
|
const buffer = new Uint8Array(memory.buffer);
|
|
36
47
|
const maxLen = Math.min(bytes.length, buffer.length - WASM_INPUT_OFFSET - 1);
|
|
37
48
|
buffer.set(bytes.subarray(0, maxLen), WASM_INPUT_OFFSET);
|
|
@@ -45,8 +56,8 @@ var CALIBRATION = {
|
|
|
45
56
|
"claude-opus-4.7": 2.05,
|
|
46
57
|
"claude-sonnet": 2.05,
|
|
47
58
|
"claude-haiku": 1.45,
|
|
48
|
-
"gemini-3.1-pro": 1.
|
|
49
|
-
"gemini-2.5-flash": 1.
|
|
59
|
+
"gemini-3.1-pro": 1.45,
|
|
60
|
+
"gemini-2.5-flash": 1.45,
|
|
50
61
|
"grok-4.20": 1.15,
|
|
51
62
|
"grok-4-1-fast": 1.15,
|
|
52
63
|
"gpt-5.4": 1.15,
|
package/dist/slash.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getInstance, writeToMemory } from './wasm.js';
|
|
1
|
+
import { getInstance, writeToMemory, ensureCapacity } from './wasm.js';
|
|
2
2
|
const WASM_INPUT_OFFSET = 4096;
|
|
3
3
|
/**
|
|
4
4
|
* Per-model calibration factors.
|
|
@@ -36,20 +36,24 @@ const WASM_INPUT_OFFSET = 4096;
|
|
|
36
36
|
* claude-haiku (routed to claude-haiku-4.5): 1.45 (was 1.40) — drifts
|
|
37
37
|
* less than the two above but still worsened under the
|
|
38
38
|
* bigger corpus (min ratio 0.744, was 0.762)
|
|
39
|
-
* gemini-3.1-pro / gemini-2.5-flash: 1.40 —
|
|
40
|
-
*
|
|
41
|
-
* / gemini-flash-latest
|
|
42
|
-
* for both — pro/flash share one
|
|
39
|
+
* gemini-3.1-pro / gemini-2.5-flash: 1.45 (was 1.40) — re-verified
|
|
40
|
+
* 2026-08-23 against the 29-sample corpus via Google's free
|
|
41
|
+
* countTokens endpoint (gemini-pro-latest / gemini-flash-latest
|
|
42
|
+
* aliases; identical ratios for both — pro/flash share one
|
|
43
|
+
* tokenizer this generation). Worst case shifted from the
|
|
44
|
+
* original 9-sample corpus: now graphql-response JSON (ratio
|
|
45
|
+
* 0.749, needs >=1.335), not the prose sample that mattered
|
|
46
|
+
* before. 1.40 was technically still safe here (unlike
|
|
47
|
+
* Claude's factor, which actually failed) but at the thinnest
|
|
48
|
+
* margin of any provider (4.9%) — bumped to match the ~6-8%
|
|
49
|
+
* band used everywhere else rather than leave the one factor
|
|
50
|
+
* most likely to fail next time this corpus grows again.
|
|
43
51
|
* Note: 'gemini-3.1-pro' as a literal model ID does not exist
|
|
44
52
|
* on the live API (404) — same stale-hardcoded-ID class of bug
|
|
45
53
|
* as the retired Claude snapshots, caught the same day. See
|
|
46
54
|
* intercept.ts MODEL_API_NAMES: the wire name is now the
|
|
47
55
|
* '-latest' alias, which sidesteps this whole bug class going
|
|
48
56
|
* forward since Google repoints it, not us.
|
|
49
|
-
* ⚠ NOT YET RE-VERIFIED against the 29-sample corpus — measured
|
|
50
|
-
* only against the original 9. Given Claude's factor needed a
|
|
51
|
-
* ~11% bump once Spanish/SQL/bash samples were added, treat
|
|
52
|
-
* this number as provisional until re-run.
|
|
53
57
|
* gpt-5.4 / gpt-5.4-mini / gpt-5.4-nano: 1.15 — measured 2026-08-23
|
|
54
58
|
* against js-tiktoken's o200k_base (free, local, exact — same
|
|
55
59
|
* encoding for the whole GPT-5.4 family per OpenAI's own
|
|
@@ -61,31 +65,28 @@ const WASM_INPUT_OFFSET = 4096;
|
|
|
61
65
|
* DEFAULT_UNKNOWN_MODEL_FACTOR (1.85), a ~60% larger correction
|
|
62
66
|
* than it needed. Still safe either way (1.85 > required
|
|
63
67
|
* minimum), just needlessly inflated for real GPT users.
|
|
64
|
-
* grok-4.20 / grok-4-1-fast: 1.15 —
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
68
|
+
* grok-4.20 / grok-4-1-fast: 1.15 — re-verified 2026-08-23 against the
|
|
69
|
+
* 29-sample corpus (20 new samples: more languages, Spanish/
|
|
70
|
+
* Japanese prose, more JSON shapes) and UNCHANGED — same
|
|
71
|
+
* worst case (technical-docs prose, ratio 0.928) as the
|
|
72
|
+
* original 9-sample measurement. The only one of the three
|
|
73
|
+
* non-GPT providers whose factor held without a bump; Claude
|
|
74
|
+
* and Gemini both needed one, on different content types each
|
|
75
|
+
* (Spanish prose, then JSON). xAI has no free count-tokens
|
|
76
|
+
* endpoint; this used real chat completions (max_tokens: 1)
|
|
77
|
+
* with a per-model baseline subtracted to remove xAI's fixed
|
|
78
|
+
* system-preamble overhead (~185-193 tokens, mostly cached)
|
|
79
|
+
* from the content-only count.
|
|
72
80
|
* Both literal API IDs ('grok-4.20', 'grok-4-1-fast') are
|
|
73
81
|
* fully retired — not just old snapshots, gone entirely —
|
|
74
82
|
* remapped to grok-4.20-0309-non-reasoning / grok-4.3 in
|
|
75
83
|
* intercept.ts MODEL_API_NAMES the same day this was found.
|
|
76
|
-
* ⚠ NOT YET RE-VERIFIED against the 29-sample corpus — same
|
|
77
|
-
* caveat as Gemini above. Costs real (small) money per run,
|
|
78
|
-
* unlike Gemini/Anthropic, so re-verify deliberately, not
|
|
79
|
-
* reflexively, but don't skip it — Grok has not been tested
|
|
80
|
-
* against non-English content or SQL/bash at all yet, and
|
|
81
|
-
* those are exactly what broke Claude's factor.
|
|
82
84
|
*
|
|
83
|
-
*
|
|
84
|
-
* corpus (expanded 2026-08-23 from the original 9,
|
|
85
|
-
* slash-tokens' own code/docs — not representative
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
* before trusting those two numbers as final.
|
|
85
|
+
* All four providers (Claude, Gemini, Grok, GPT) are now verified against
|
|
86
|
+
* the same 29-sample corpus (expanded 2026-08-23 from the original 9,
|
|
87
|
+
* which was mostly slash-tokens' own code/docs — not representative
|
|
88
|
+
* third-party content). Re-verify again if the corpus grows further —
|
|
89
|
+
* Claude and Gemini both needed real bumps the first time it grew.
|
|
89
90
|
*
|
|
90
91
|
* Slash must NEVER under-report. Over-reporting is safe (go/no-go only).
|
|
91
92
|
*/
|
|
@@ -94,8 +95,8 @@ const CALIBRATION = {
|
|
|
94
95
|
'claude-opus-4.7': 2.05,
|
|
95
96
|
'claude-sonnet': 2.05,
|
|
96
97
|
'claude-haiku': 1.45,
|
|
97
|
-
'gemini-3.1-pro': 1.
|
|
98
|
-
'gemini-2.5-flash': 1.
|
|
98
|
+
'gemini-3.1-pro': 1.45,
|
|
99
|
+
'gemini-2.5-flash': 1.45,
|
|
99
100
|
'grok-4.20': 1.15,
|
|
100
101
|
'grok-4-1-fast': 1.15,
|
|
101
102
|
'gpt-5.4': 1.15,
|
|
@@ -139,6 +140,7 @@ export function slashBytes(bytes) {
|
|
|
139
140
|
return 0;
|
|
140
141
|
const instance = getInstance();
|
|
141
142
|
const memory = instance.exports.memory;
|
|
143
|
+
ensureCapacity(memory, WASM_INPUT_OFFSET + bytes.length + 1);
|
|
142
144
|
const buffer = new Uint8Array(memory.buffer);
|
|
143
145
|
const maxLen = Math.min(bytes.length, buffer.length - WASM_INPUT_OFFSET - 1);
|
|
144
146
|
buffer.set(bytes.subarray(0, maxLen), WASM_INPUT_OFFSET);
|
package/dist/wasm.d.ts
CHANGED
|
@@ -1,2 +1,18 @@
|
|
|
1
1
|
export declare function getInstance(): WebAssembly.Instance;
|
|
2
|
+
/**
|
|
3
|
+
* Grow WASM linear memory if needed so `neededBytes` of input (plus the
|
|
4
|
+
* offset reserved for Zig's stack/globals) fits without truncation.
|
|
5
|
+
*
|
|
6
|
+
* Fixed 2026-08-23: writeToMemory() used to silently truncate any input
|
|
7
|
+
* larger than the WASM module's initial memory (~1.06MB usable) BEFORE
|
|
8
|
+
* estimate_tokens ever ran — no error, no flag, just a token count
|
|
9
|
+
* computed over a truncated prefix. This defeated the "never
|
|
10
|
+
* under-report" invariant exactly where it matters most: calls near a
|
|
11
|
+
* model's real context ceiling. WebAssembly.Memory is growable at
|
|
12
|
+
* runtime (confirmed: `memory.grow()` works on this module, 17 pages ->
|
|
13
|
+
* 117 pages tested directly) — growing does not move or invalidate
|
|
14
|
+
* existing data, it only appends new zeroed pages, so this is safe to
|
|
15
|
+
* do lazily on every call with no effect on correctness for small inputs.
|
|
16
|
+
*/
|
|
17
|
+
export declare function ensureCapacity(memory: WebAssembly.Memory, neededBytes: number): void;
|
|
2
18
|
export declare function writeToMemory(content: string): number;
|
package/dist/wasm.js
CHANGED
|
@@ -23,10 +23,46 @@ export function getInstance() {
|
|
|
23
23
|
_instance = new WebAssembly.Instance(module);
|
|
24
24
|
return _instance;
|
|
25
25
|
}
|
|
26
|
+
// Safety cap on how far we'll grow WASM linear memory for one input.
|
|
27
|
+
// 32 MB is a large margin over any real model's context window today
|
|
28
|
+
// (the biggest is 1M tokens, which even for dense multi-byte scripts
|
|
29
|
+
// like CJK doesn't approach 32MB of UTF-8 bytes) — so growth alone
|
|
30
|
+
// eliminates silent truncation for every realistic real-world call.
|
|
31
|
+
// Above this cap we still truncate (see below), but a truncated
|
|
32
|
+
// estimate at 32MB+ of input is still far larger than any model's
|
|
33
|
+
// context ceiling, so the overflow gate still fires correctly either
|
|
34
|
+
// way — the cap exists only to bound memory growth against a
|
|
35
|
+
// pathological/abusive input, not to protect estimate correctness.
|
|
36
|
+
const MAX_WASM_INPUT_BYTES = 32 * 1024 * 1024;
|
|
37
|
+
const WASM_PAGE_BYTES = 65536;
|
|
38
|
+
/**
|
|
39
|
+
* Grow WASM linear memory if needed so `neededBytes` of input (plus the
|
|
40
|
+
* offset reserved for Zig's stack/globals) fits without truncation.
|
|
41
|
+
*
|
|
42
|
+
* Fixed 2026-08-23: writeToMemory() used to silently truncate any input
|
|
43
|
+
* larger than the WASM module's initial memory (~1.06MB usable) BEFORE
|
|
44
|
+
* estimate_tokens ever ran — no error, no flag, just a token count
|
|
45
|
+
* computed over a truncated prefix. This defeated the "never
|
|
46
|
+
* under-report" invariant exactly where it matters most: calls near a
|
|
47
|
+
* model's real context ceiling. WebAssembly.Memory is growable at
|
|
48
|
+
* runtime (confirmed: `memory.grow()` works on this module, 17 pages ->
|
|
49
|
+
* 117 pages tested directly) — growing does not move or invalidate
|
|
50
|
+
* existing data, it only appends new zeroed pages, so this is safe to
|
|
51
|
+
* do lazily on every call with no effect on correctness for small inputs.
|
|
52
|
+
*/
|
|
53
|
+
export function ensureCapacity(memory, neededBytes) {
|
|
54
|
+
const cappedNeeded = Math.min(neededBytes, MAX_WASM_INPUT_BYTES);
|
|
55
|
+
if (memory.buffer.byteLength >= cappedNeeded)
|
|
56
|
+
return;
|
|
57
|
+
const additionalPages = Math.ceil((cappedNeeded - memory.buffer.byteLength) / WASM_PAGE_BYTES);
|
|
58
|
+
if (additionalPages > 0)
|
|
59
|
+
memory.grow(additionalPages);
|
|
60
|
+
}
|
|
26
61
|
export function writeToMemory(content) {
|
|
27
62
|
const instance = getInstance();
|
|
28
63
|
const bytes = encoder.encode(content);
|
|
29
64
|
const memory = instance.exports.memory;
|
|
65
|
+
ensureCapacity(memory, WASM_INPUT_OFFSET + bytes.length + 1);
|
|
30
66
|
const buffer = new Uint8Array(memory.buffer);
|
|
31
67
|
const maxLen = Math.min(bytes.length, buffer.length - WASM_INPUT_OFFSET - 1);
|
|
32
68
|
buffer.set(bytes.subarray(0, maxLen), WASM_INPUT_OFFSET);
|