@sliday/tamp 0.2.5 → 0.2.7
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/bin/tamp.js +3 -2
- package/index.js +21 -5
- package/package.json +3 -2
- package/stats.js +48 -19
package/bin/tamp.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createProxy } from '../index.js'
|
|
3
|
-
import { existsSync } from 'node:fs'
|
|
3
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
4
4
|
import { spawn } from 'node:child_process'
|
|
5
5
|
import { fileURLToPath } from 'node:url'
|
|
6
6
|
import { dirname, join } from 'node:path'
|
|
7
7
|
|
|
8
8
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
9
9
|
const root = join(__dirname, '..')
|
|
10
|
+
const pkg = JSON.parse(readFileSync(join(root, 'package.json'), 'utf-8'))
|
|
10
11
|
|
|
11
12
|
// ANSI colors
|
|
12
13
|
const c = {
|
|
@@ -28,7 +29,7 @@ function printBanner(config) {
|
|
|
28
29
|
const url = `http://localhost:${config.port}`
|
|
29
30
|
|
|
30
31
|
log('')
|
|
31
|
-
log(` ${c.bold}${c.cyan}┌─ Tamp
|
|
32
|
+
log(` ${c.bold}${c.cyan}┌─ Tamp ${c.dim}v${pkg.version}${c.reset}${c.bold}${c.cyan} ───────────────────────────────┐${c.reset}`)
|
|
32
33
|
log(` ${c.cyan}│${c.reset} Proxy: ${c.bold}${c.green}${url}${c.reset}${c.cyan} │${c.reset}`)
|
|
33
34
|
log(` ${c.cyan}│${c.reset} Status: ${c.bgGreen}${c.bold} ● READY ${c.reset}${c.cyan} │${c.reset}`)
|
|
34
35
|
log(` ${c.cyan}│${c.reset} ${c.cyan}│${c.reset}`)
|
package/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import http from 'node:http'
|
|
2
2
|
import https from 'node:https'
|
|
3
3
|
import zlib from 'node:zlib'
|
|
4
|
+
import * as fzstd from 'fzstd'
|
|
4
5
|
import { loadConfig } from './config.js'
|
|
5
6
|
import { compressRequest } from './compress.js'
|
|
6
7
|
import { detectProvider } from './providers.js'
|
|
@@ -139,21 +140,36 @@ return http.createServer(async (req, res) => {
|
|
|
139
140
|
const headers = { ...req.headers }
|
|
140
141
|
delete headers.host
|
|
141
142
|
|
|
142
|
-
// Decompress
|
|
143
|
+
// Decompress request body if content-encoding is set
|
|
143
144
|
const encoding = (req.headers['content-encoding'] || '').toLowerCase()
|
|
144
145
|
let textBody
|
|
146
|
+
let decompressed = false
|
|
145
147
|
try {
|
|
146
148
|
if (encoding === 'gzip') {
|
|
147
149
|
textBody = zlib.gunzipSync(rawBody)
|
|
150
|
+
decompressed = true
|
|
148
151
|
} else if (encoding === 'deflate') {
|
|
149
152
|
textBody = zlib.inflateSync(rawBody)
|
|
153
|
+
decompressed = true
|
|
150
154
|
} else if (encoding === 'br') {
|
|
151
155
|
textBody = zlib.brotliDecompressSync(rawBody)
|
|
156
|
+
decompressed = true
|
|
157
|
+
} else if (encoding === 'zstd') {
|
|
158
|
+
textBody = Buffer.from(fzstd.decompress(new Uint8Array(rawBody)))
|
|
159
|
+
decompressed = true
|
|
160
|
+
} else if (encoding && encoding !== 'identity') {
|
|
161
|
+
// Unknown encoding — can't decompress, passthrough as-is
|
|
162
|
+
if (config.log) console.error(`[tamp] passthrough (unsupported encoding: ${encoding})`)
|
|
163
|
+
forwardRequest(req.method, upstreamUrl, headers, rawBody, res)
|
|
164
|
+
return
|
|
152
165
|
} else {
|
|
153
166
|
textBody = rawBody
|
|
154
167
|
}
|
|
155
168
|
} catch {
|
|
156
|
-
|
|
169
|
+
// Decompression failed — passthrough original body
|
|
170
|
+
if (config.log) console.error(`[tamp] passthrough (decompression failed)`)
|
|
171
|
+
forwardRequest(req.method, upstreamUrl, headers, rawBody, res)
|
|
172
|
+
return
|
|
157
173
|
}
|
|
158
174
|
|
|
159
175
|
try {
|
|
@@ -161,11 +177,11 @@ return http.createServer(async (req, res) => {
|
|
|
161
177
|
const { body, stats } = await compressRequest(parsed, config, provider)
|
|
162
178
|
finalBody = Buffer.from(JSON.stringify(body), 'utf-8')
|
|
163
179
|
// Send uncompressed — simpler and content-length is accurate
|
|
164
|
-
delete headers['content-encoding']
|
|
180
|
+
if (decompressed) delete headers['content-encoding']
|
|
165
181
|
|
|
166
|
-
if (config.log
|
|
182
|
+
if (config.log) {
|
|
167
183
|
session.record(stats)
|
|
168
|
-
console.error(formatRequestLog(stats, session, provider.name, req.url))
|
|
184
|
+
console.error(formatRequestLog(stats, session, provider.name, req.url, textBody.length))
|
|
169
185
|
}
|
|
170
186
|
} catch (err) {
|
|
171
187
|
if (config.log) console.error(`[tamp] passthrough (parse error): ${err.message}`)
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"providers.js",
|
|
10
10
|
"stats.js"
|
|
11
11
|
],
|
|
12
|
-
"version": "0.2.
|
|
12
|
+
"version": "0.2.7",
|
|
13
13
|
"description": "Token compression proxy for coding agents. Works with Claude Code, Aider, Cursor, Cline, Windsurf. 33.9% fewer input tokens.",
|
|
14
14
|
"type": "module",
|
|
15
15
|
"main": "index.js",
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"homepage": "https://github.com/sliday/tamp",
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"@anthropic-ai/tokenizer": "^0.0.4",
|
|
42
|
-
"@toon-format/toon": "^2.1.0"
|
|
42
|
+
"@toon-format/toon": "^2.1.0",
|
|
43
|
+
"fzstd": "^0.1.1"
|
|
43
44
|
}
|
|
44
45
|
}
|
package/stats.js
CHANGED
|
@@ -1,40 +1,68 @@
|
|
|
1
|
-
|
|
1
|
+
const c = {
|
|
2
|
+
reset: '\x1b[0m',
|
|
3
|
+
bold: '\x1b[1m',
|
|
4
|
+
dim: '\x1b[2m',
|
|
5
|
+
green: '\x1b[32m',
|
|
6
|
+
yellow: '\x1b[33m',
|
|
7
|
+
cyan: '\x1b[36m',
|
|
8
|
+
magenta: '\x1b[35m',
|
|
9
|
+
red: '\x1b[31m',
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function formatRequestLog(stats, session, providerName, url, bodySize) {
|
|
2
13
|
const compressed = stats.filter(s => s.method)
|
|
3
|
-
const skipped = stats.filter(s => s.skipped)
|
|
4
14
|
const label = providerName || 'anthropic'
|
|
5
15
|
const path = url || '/v1/messages'
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
+
const sizeInfo = bodySize ? ` ${c.dim}${fmtSize(bodySize)}${c.reset}` : ''
|
|
17
|
+
|
|
18
|
+
if (!compressed.length && !stats.length) {
|
|
19
|
+
return `[tamp] ${c.cyan}${label}${c.reset} ${path}${sizeInfo} ${c.dim}— no tool blocks${c.reset}`
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!compressed.length) {
|
|
23
|
+
const skipCount = stats.filter(s => s.skipped).length
|
|
24
|
+
const reason = skipCount ? `${skipCount} skipped` : 'nothing to compress'
|
|
25
|
+
return `[tamp] ${c.cyan}${label}${c.reset} ${path}${sizeInfo} ${c.dim}— ${stats.length} blocks, ${reason}${c.reset}`
|
|
16
26
|
}
|
|
17
27
|
|
|
18
28
|
const totalOrig = compressed.reduce((a, s) => a + s.originalLen, 0)
|
|
19
29
|
const totalComp = compressed.reduce((a, s) => a + s.compressedLen, 0)
|
|
20
30
|
const totalOrigTok = compressed.reduce((a, s) => a + (s.originalTokens || 0), 0)
|
|
21
31
|
const totalCompTok = compressed.reduce((a, s) => a + (s.compressedTokens || 0), 0)
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
32
|
+
const pct = (((totalOrig - totalComp) / totalOrig) * 100).toFixed(1)
|
|
33
|
+
const saved = totalOrig - totalComp
|
|
34
|
+
const tokSaved = totalOrigTok - totalCompTok
|
|
35
|
+
|
|
36
|
+
const lines = []
|
|
37
|
+
lines.push(`[tamp] ${c.cyan}${label}${c.reset} ${path}${sizeInfo} ${c.green}— ${compressed.length} compressed, -${pct}%${c.reset}`)
|
|
38
|
+
|
|
39
|
+
for (const s of compressed) {
|
|
40
|
+
const sPct = (((s.originalLen - s.compressedLen) / s.originalLen) * 100).toFixed(1)
|
|
41
|
+
const tokInfo = s.originalTokens ? ` ${c.dim}${s.originalTokens}→${s.compressedTokens} tok${c.reset}` : ''
|
|
42
|
+
lines.push(`[tamp] ${c.dim}block[${s.index}]${c.reset} ${fmtSize(s.originalLen)}→${fmtSize(s.compressedLen)} ${c.green}-${sPct}%${c.reset}${tokInfo} ${c.dim}[${s.method}]${c.reset}`)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
for (const s of stats.filter(s => s.skipped)) {
|
|
46
|
+
lines.push(`[tamp] ${c.dim}block[${s.index}] skipped (${s.skipped})${c.reset}`)
|
|
26
47
|
}
|
|
27
48
|
|
|
28
49
|
if (session) {
|
|
29
|
-
const
|
|
30
|
-
|
|
50
|
+
const t = session.getTotals()
|
|
51
|
+
const sessionPct = t.totalOriginal > 0 ? (((t.totalSaved) / t.totalOriginal) * 100).toFixed(1) : '0.0'
|
|
52
|
+
lines.push(`[tamp] ${c.magenta}session${c.reset} ${fmtSize(t.totalSaved)} saved across ${t.compressionCount} blocks ${c.dim}(${sessionPct}% avg)${c.reset}`)
|
|
31
53
|
}
|
|
32
54
|
|
|
33
55
|
return lines.join('\n')
|
|
34
56
|
}
|
|
35
57
|
|
|
58
|
+
function fmtSize(n) {
|
|
59
|
+
if (n >= 1024) return (n / 1024).toFixed(1) + 'k'
|
|
60
|
+
return n + ''
|
|
61
|
+
}
|
|
62
|
+
|
|
36
63
|
export function createSession() {
|
|
37
64
|
let totalSaved = 0
|
|
65
|
+
let totalOriginal = 0
|
|
38
66
|
let totalTokensSaved = 0
|
|
39
67
|
let compressionCount = 0
|
|
40
68
|
|
|
@@ -43,13 +71,14 @@ export function createSession() {
|
|
|
43
71
|
for (const s of stats) {
|
|
44
72
|
if (s.method && s.originalLen && s.compressedLen) {
|
|
45
73
|
totalSaved += s.originalLen - s.compressedLen
|
|
74
|
+
totalOriginal += s.originalLen
|
|
46
75
|
totalTokensSaved += (s.originalTokens || 0) - (s.compressedTokens || 0)
|
|
47
76
|
compressionCount++
|
|
48
77
|
}
|
|
49
78
|
}
|
|
50
79
|
},
|
|
51
80
|
getTotals() {
|
|
52
|
-
return { totalSaved, totalTokensSaved, compressionCount }
|
|
81
|
+
return { totalSaved, totalOriginal, totalTokensSaved, compressionCount }
|
|
53
82
|
},
|
|
54
83
|
}
|
|
55
84
|
}
|