@sliday/tamp 0.2.4 → 0.2.6
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 +10 -2
- package/bin/tamp.js +3 -2
- package/index.js +37 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Tamp
|
|
2
2
|
|
|
3
|
-
**Token compression proxy for coding agents.** 33.9% fewer input tokens, zero code changes. Works with Claude Code, Aider, Cursor, Cline, Windsurf, and any OpenAI-compatible agent.
|
|
3
|
+
**Token compression proxy for coding agents.** 33.9% fewer input tokens, zero code changes. Works with Claude Code, Codex, Aider, Cursor, Cline, Windsurf, and any OpenAI-compatible agent.
|
|
4
4
|
|
|
5
5
|
```
|
|
6
6
|
npx @sliday/tamp
|
|
@@ -19,6 +19,7 @@ Tamp auto-detects your agent's API format and compresses tool result blocks befo
|
|
|
19
19
|
```
|
|
20
20
|
Claude Code ──► Tamp (localhost:7778) ──► Anthropic API
|
|
21
21
|
Aider/Cursor ──► │ ──► OpenAI API
|
|
22
|
+
Codex CLI ─────► │ ──► OpenAI API
|
|
22
23
|
Gemini CLI ────► │ ──► Google AI API
|
|
23
24
|
│
|
|
24
25
|
├─ JSON → minify whitespace
|
|
@@ -34,6 +35,7 @@ Gemini CLI ────► │ ──► Google AI API
|
|
|
34
35
|
|--------|----------|--------|
|
|
35
36
|
| Anthropic Messages | `POST /v1/messages` | Claude Code |
|
|
36
37
|
| OpenAI Chat Completions | `POST /v1/chat/completions` | Aider, Cursor, Cline, Windsurf, OpenCode |
|
|
38
|
+
| OpenAI Responses | `POST /v1/responses` | Codex CLI |
|
|
37
39
|
| Google Gemini | `POST .../generateContent` | Gemini CLI |
|
|
38
40
|
|
|
39
41
|
### Compression Stages
|
|
@@ -81,6 +83,12 @@ export OPENAI_API_BASE=http://localhost:7778
|
|
|
81
83
|
aider
|
|
82
84
|
```
|
|
83
85
|
|
|
86
|
+
**Codex CLI:**
|
|
87
|
+
Add to `~/.codex/config.toml`:
|
|
88
|
+
```toml
|
|
89
|
+
openai_base_url = "http://localhost:7778"
|
|
90
|
+
```
|
|
91
|
+
|
|
84
92
|
**Cursor / Cline / Windsurf:**
|
|
85
93
|
Set the API base URL to `http://localhost:7778` in your editor's settings.
|
|
86
94
|
|
|
@@ -96,7 +104,7 @@ All configuration via environment variables:
|
|
|
96
104
|
| `TAMP_UPSTREAM` | `https://api.anthropic.com` | Default upstream API URL |
|
|
97
105
|
| `TAMP_UPSTREAM_OPENAI` | `https://api.openai.com` | Upstream for OpenAI-format requests |
|
|
98
106
|
| `TAMP_UPSTREAM_GEMINI` | `https://generativelanguage.googleapis.com` | Upstream for Gemini-format requests |
|
|
99
|
-
| `TAMP_STAGES` | `minify` | Comma-separated compression stages |
|
|
107
|
+
| `TAMP_STAGES` | `minify,toon` | Comma-separated compression stages |
|
|
100
108
|
| `TAMP_MIN_SIZE` | `200` | Minimum content size (chars) to attempt compression |
|
|
101
109
|
| `TAMP_LOG` | `true` | Enable request logging to stderr |
|
|
102
110
|
| `TAMP_LOG_FILE` | _(none)_ | Write logs to file |
|
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,5 +1,7 @@
|
|
|
1
1
|
import http from 'node:http'
|
|
2
2
|
import https from 'node:https'
|
|
3
|
+
import zlib from 'node:zlib'
|
|
4
|
+
import * as fzstd from 'fzstd'
|
|
3
5
|
import { loadConfig } from './config.js'
|
|
4
6
|
import { compressRequest } from './compress.js'
|
|
5
7
|
import { detectProvider } from './providers.js'
|
|
@@ -138,10 +140,44 @@ return http.createServer(async (req, res) => {
|
|
|
138
140
|
const headers = { ...req.headers }
|
|
139
141
|
delete headers.host
|
|
140
142
|
|
|
143
|
+
// Decompress request body if content-encoding is set
|
|
144
|
+
const encoding = (req.headers['content-encoding'] || '').toLowerCase()
|
|
145
|
+
let textBody
|
|
146
|
+
let decompressed = false
|
|
141
147
|
try {
|
|
142
|
-
|
|
148
|
+
if (encoding === 'gzip') {
|
|
149
|
+
textBody = zlib.gunzipSync(rawBody)
|
|
150
|
+
decompressed = true
|
|
151
|
+
} else if (encoding === 'deflate') {
|
|
152
|
+
textBody = zlib.inflateSync(rawBody)
|
|
153
|
+
decompressed = true
|
|
154
|
+
} else if (encoding === 'br') {
|
|
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
|
|
165
|
+
} else {
|
|
166
|
+
textBody = rawBody
|
|
167
|
+
}
|
|
168
|
+
} catch {
|
|
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
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
try {
|
|
176
|
+
const parsed = JSON.parse(textBody.toString('utf-8'))
|
|
143
177
|
const { body, stats } = await compressRequest(parsed, config, provider)
|
|
144
178
|
finalBody = Buffer.from(JSON.stringify(body), 'utf-8')
|
|
179
|
+
// Send uncompressed — simpler and content-length is accurate
|
|
180
|
+
if (decompressed) delete headers['content-encoding']
|
|
145
181
|
|
|
146
182
|
if (config.log && stats.length) {
|
|
147
183
|
session.record(stats)
|
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.6",
|
|
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
|
}
|