@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 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 ─────────────────────────────────┐${c.reset}`)
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 gzip/deflate/br if needed
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
- textBody = rawBody
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 && stats.length) {
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.5",
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
- export function formatRequestLog(stats, session, providerName, url) {
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 lines = [`[tamp] ${label} ${path}${stats.length} blocks, ${compressed.length} compressed`]
7
-
8
- for (const s of stats) {
9
- if (s.skipped) {
10
- lines.push(`[tamp] block[${s.index}]: skipped (${s.skipped})`)
11
- } else if (s.method) {
12
- const pct = (((s.originalLen - s.compressedLen) / s.originalLen) * 100).toFixed(1)
13
- const tokInfo = s.originalTokens ? ` ${s.originalTokens}->${s.compressedTokens} tok` : ''
14
- lines.push(`[tamp] block[${s.index}]: ${s.originalLen}->${s.compressedLen} chars (-${pct}%)${tokInfo} [${s.method}]`)
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
- if (compressed.length > 0) {
23
- const pct = (((totalOrig - totalComp) / totalOrig) * 100).toFixed(1)
24
- const tokPct = totalOrigTok > 0 ? (((totalOrigTok - totalCompTok) / totalOrigTok) * 100).toFixed(1) : '0.0'
25
- lines.push(`[tamp] total: ${totalOrig}->${totalComp} chars (-${pct}%), ${totalOrigTok}->${totalCompTok} tokens (-${tokPct}%)`)
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 totals = session.getTotals()
30
- lines.push(`[tamp] session: ${totals.totalSaved} chars, ${totals.totalTokensSaved} tokens saved across ${totals.compressionCount} compressions`)
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
  }