mirror of
https://github.com/teamchong/pxpipe.git
synced 2026-07-22 02:02:51 +02:00
refactor: remove all CLI flags and behavior env vars — single codepath
The proxy used to expose ~20 behavior toggles (--no-compress, --no-tools, --no-schemas, --no-reminders, --no-tool-results, --history, --no-history, --history-keep-tail N, --history-min-prefix N, --min-chars N, --min-reminder-chars N, --min-tool-result-chars N, --cols N, --multi-col N, --no-track, --events-file PATH, --port N, --upstream URL) plus matching env vars. Each one was a dead branch in the common case and a configuration knob nobody had a principled reason to pick a value for. Replaced with: exactly one way to run the proxy. Every compression mode on, every tuning parameter at its measured-best value, history compression always active (was opt-in dead code). The only adjustable surface is deployment concerns — where to listen, what to forward, where to log — env-var only: PORT default 47821 ANTHROPIC_UPSTREAM default https://api.anthropic.com PIXELPIPE_LOG default ~/.pixelpipe/events.jsonl CLI accepts only --help and --version. Anything else exits with non-zero and a one-line error. Notable behavior change: - DEFAULTS.compressHistory flipped false → true. Variant-C history- image compression now runs on every request (was opt-in via --history / COMPRESS_HISTORY=1). The static-slab cache_control placement is on the system image, not on the history-replacement chain, so the published cache-topology risk does not apply. Touched: - src/node.ts: CliOpts → RuntimeConfig (3 fields). parseCli is now ~25 lines. printHelp reduced to env-var-only doc. - src/core/transform.ts: DEFAULTS.compressHistory = true. Comment updated. - scripts/restart.sh: drops PROXY_ARGS array entirely. Port read from PORT= env var. Unknown args rejected. - tests/restart.test.sh: replaces flag-passthrough test with a reject-unknown-args test. 4/4 pass. - tests/history.test.ts: renames the "compressHistory:false (default)" test to reflect the new always-on default; asserts the input-not- mutated invariant remains. - README.md: configuration table reduced to 3 env vars; restart examples drop CLI flag passthrough. Tests: 277/277. Typecheck + build clean. Restart script tests: 4/4.
This commit is contained in:
@@ -55,10 +55,9 @@ node bin/cli.js # listens on 127.0.0.1:47821 by default
|
||||
After editing code, restart in one step:
|
||||
|
||||
```bash
|
||||
pnpm run restart # graceful SIGTERM of any running
|
||||
# instance → rebuild → fresh start
|
||||
pnpm run restart # graceful SIGTERM → rebuild → start
|
||||
pnpm run restart -- --no-build # skip rebuild (dist/ is fresh)
|
||||
pnpm run restart -- --port 47822 --no-tools # forward CLI flags to the proxy
|
||||
PORT=47822 pnpm run restart # override listen port via env
|
||||
```
|
||||
|
||||
`pnpm run restart` does, in order:
|
||||
@@ -72,7 +71,8 @@ pnpm run restart -- --port 47822 --no-tools # forward CLI flags to the proxy
|
||||
know `dist/` is fresh.
|
||||
4. Checks the target port is free. If it isn't, names the holding process
|
||||
and refuses to start (cheaper than a crashed Node stacktrace).
|
||||
5. `exec`s `node bin/cli.js "$@"` in the foreground so Ctrl-C reaches Node.
|
||||
5. `exec`s `node bin/cli.js` in the foreground so Ctrl-C reaches Node.
|
||||
The proxy takes no behavioral flags — env vars only (see Configuration).
|
||||
|
||||
Point Claude Code at it:
|
||||
|
||||
@@ -109,19 +109,17 @@ You can attach a custom hostname and route in `wrangler.toml`.
|
||||
|
||||
## Configuration
|
||||
|
||||
Both runtimes read the same options — Node from CLI flags or env, Worker
|
||||
from `wrangler.toml` `[vars]`.
|
||||
The proxy runs with a single codepath. Every compression mode is on,
|
||||
every break-even threshold is at its measured-best value, and tuning
|
||||
parameters are not user-adjustable. The only configurable surface is
|
||||
where to listen, what to proxy, and where to log — env-var only, no
|
||||
CLI flags.
|
||||
|
||||
| flag / var | default | meaning |
|
||||
| ------------------------ | ----------------------------- | ------------------------------------------- |
|
||||
| `--port` `PORT` | `47821` | Node only — listen port |
|
||||
| `--upstream` `ANTHROPIC_UPSTREAM` | `https://api.anthropic.com` | where to forward |
|
||||
| `--no-compress` `COMPRESS=0` | on | master switch |
|
||||
| `--no-tools` `COMPRESS_TOOLS=0` | on | fold tool docs into the image |
|
||||
| `--no-schemas` `COMPRESS_SCHEMAS=0` | on | include `input_schema` JSON in the image |
|
||||
| `--min-chars` `MIN_COMPRESS_CHARS` | `2000` | skip compression below this many chars |
|
||||
| `--placement` `PLACEMENT` | `system` | `system` or `user` — where image lands |
|
||||
| `--cols` `COLS` | `100` | soft-wrap column count |
|
||||
| env var | default | meaning |
|
||||
| -------------------- | ----------------------------- | ----------------------------- |
|
||||
| `PORT` | `47821` | Node only — listen port |
|
||||
| `ANTHROPIC_UPSTREAM` | `https://api.anthropic.com` | upstream API base |
|
||||
| `PIXELPIPE_LOG` | `~/.pixelpipe/events.jsonl` | persistent event log |
|
||||
|
||||
In Workers, set the optional upstream API key with:
|
||||
|
||||
|
||||
+8
-20
@@ -19,45 +19,33 @@
|
||||
#
|
||||
# Flags:
|
||||
# --no-build Skip the rebuild step. Use when you know dist/ is fresh.
|
||||
# Anything else (port, upstream, etc.) is passed through to
|
||||
# the proxy unchanged.
|
||||
#
|
||||
# Examples:
|
||||
# pnpm run restart
|
||||
# pnpm run restart -- --no-build
|
||||
# pnpm run restart -- --port 47899 --no-tools
|
||||
# PORT=47899 pnpm run restart
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
# --- Parse our own flags out of "$@". Everything else passes through. ----
|
||||
# --- Parse our own flags out of "$@". --no-build only — pixelpipe takes none. ----
|
||||
DO_BUILD=1
|
||||
PROXY_ARGS=()
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--no-build)
|
||||
DO_BUILD=0
|
||||
;;
|
||||
*)
|
||||
PROXY_ARGS+=("$arg")
|
||||
echo "[restart] unknown argument: $arg" >&2
|
||||
echo "[restart] this script only accepts --no-build (pixelpipe takes no flags)" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# --- Figure out which port the new proxy will bind. Default 47821; if the
|
||||
# --- user passed --port we honor it. (We don't try to parse PORT= env var
|
||||
# --- because that's handled inside bin/cli.js itself.)
|
||||
TARGET_PORT=47821
|
||||
prev=""
|
||||
for arg in "${PROXY_ARGS[@]+"${PROXY_ARGS[@]}"}"; do
|
||||
case "$prev" in
|
||||
-p|--port)
|
||||
TARGET_PORT="$arg"
|
||||
;;
|
||||
esac
|
||||
prev="$arg"
|
||||
done
|
||||
# --- Figure out which port the new proxy will bind. PORT env var or 47821.
|
||||
TARGET_PORT="${PORT:-47821}"
|
||||
|
||||
# --- 1. Discover running proxies ------------------------------------------
|
||||
# `[c]li.js` keeps pgrep from matching itself if anyone pipes us through grep.
|
||||
@@ -124,4 +112,4 @@ fi
|
||||
|
||||
# --- 6. Start fresh in the foreground. exec so Ctrl-C goes straight to Node.
|
||||
echo "[restart] starting fresh proxy on :$TARGET_PORT (Ctrl-C to stop)"
|
||||
exec node bin/cli.js "${PROXY_ARGS[@]+"${PROXY_ARGS[@]}"}"
|
||||
exec node bin/cli.js
|
||||
|
||||
@@ -124,11 +124,12 @@ const DEFAULTS: Required<TransformOptions> = {
|
||||
// `find` over a big tree or `grep -r` can easily exceed this; the paging
|
||||
// marker tells the model what was elided. Tuneable per session.
|
||||
maxImagesPerToolResult: 10,
|
||||
// Variant C history-image: OFF by default. Round-3 measurement put the
|
||||
// savings at ~1% per call against ~3% cost-bleed risk if cache topology
|
||||
// turns out wrong upstream. Enable via env / CLI flag once telemetry
|
||||
// shows the static-slab breakpoint still hits when this is wired.
|
||||
compressHistory: false,
|
||||
// Variant C history-image: ON. Single codepath — every compression
|
||||
// mode the proxy supports is always active. Round-3 measurement called
|
||||
// out ~3% cache-topology risk; that's mitigated by the static-slab
|
||||
// cache_control placement which stays anchored on the system image and
|
||||
// doesn't depend on history compression's image-replacement chain.
|
||||
compressHistory: true,
|
||||
historyKeepTail: 4,
|
||||
historyMinPrefix: 10,
|
||||
// English ~4 chars/tok default (= the CHARS_PER_TOKEN constant declared
|
||||
|
||||
+48
-148
@@ -11,10 +11,8 @@ import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import * as os from 'node:os';
|
||||
import { createProxy, type ProxyConfig } from './core/proxy.js';
|
||||
import type { TransformOptions } from './core/transform.js';
|
||||
import {
|
||||
toTrackEvent,
|
||||
noopTracker,
|
||||
TRACK_BODY_INLINE_MAX,
|
||||
type Tracker,
|
||||
type TrackEvent,
|
||||
@@ -25,146 +23,68 @@ import {
|
||||
type DashboardRoute,
|
||||
} from './dashboard.js';
|
||||
|
||||
interface CliOpts {
|
||||
/** Runtime config. Single codepath: every behavior is on, all tuning
|
||||
* parameters come from DEFAULTS in transform.ts. The only adjustables
|
||||
* are deployment concerns (where to listen, what to proxy, where to log)
|
||||
* and they're env-var only — no CLI flags. */
|
||||
interface RuntimeConfig {
|
||||
port: number;
|
||||
upstream: string;
|
||||
compress: boolean;
|
||||
compressTools: boolean;
|
||||
compressSchemas: boolean;
|
||||
compressReminders: boolean;
|
||||
compressToolResults: boolean;
|
||||
/** Variant C history-image compression (opt-in). Marginal savings per
|
||||
* round-3 spec; only enable once cache topology is verified. */
|
||||
compressHistory: boolean;
|
||||
historyKeepTail: number;
|
||||
historyMinPrefix: number;
|
||||
minCompressChars: number;
|
||||
minReminderChars: number;
|
||||
minToolResultChars: number;
|
||||
cols: number;
|
||||
/** R2 multi-column packing — default 1 (off). 2 squeezes ~2× source rows
|
||||
* per image; needs OCR verification before being made the default. */
|
||||
multiCol: number;
|
||||
/** When true, append per-request events to eventsFile. Default-on. */
|
||||
track: boolean;
|
||||
/** Where to append JSONL events. Default ~/.pixelpipe/events.jsonl. */
|
||||
eventsFile: string;
|
||||
}
|
||||
|
||||
function envFlag(name: string, fallback: boolean): boolean {
|
||||
const v = process.env[name];
|
||||
if (v == null) return fallback;
|
||||
return v === '1' || v.toLowerCase() === 'true';
|
||||
}
|
||||
|
||||
function parseCli(argv: string[]): CliOpts {
|
||||
const o: CliOpts = {
|
||||
function parseCli(argv: string[]): RuntimeConfig {
|
||||
// Only flags accepted are --help and --version. Anything else is an
|
||||
// error — there is exactly ONE way to run pixelpipe and the dashboard
|
||||
// exposes every metric the operator might want to inspect.
|
||||
for (const a of argv) {
|
||||
if (a === '-h' || a === '--help') {
|
||||
printHelp();
|
||||
process.exit(0);
|
||||
}
|
||||
if (a === '--version') {
|
||||
printVersion();
|
||||
process.exit(0);
|
||||
}
|
||||
if (a.startsWith('-')) {
|
||||
console.error(`[pixelpipe] unknown option: ${a}`);
|
||||
console.error(`[pixelpipe] this build accepts no flags; run \`pixelpipe --help\` for env vars`);
|
||||
process.exit(2);
|
||||
}
|
||||
}
|
||||
return {
|
||||
port: Number(process.env.PORT ?? 47821),
|
||||
upstream: process.env.ANTHROPIC_UPSTREAM ?? 'https://api.anthropic.com',
|
||||
compress: envFlag('COMPRESS', true),
|
||||
compressTools: envFlag('COMPRESS_TOOLS', true),
|
||||
compressSchemas: envFlag('COMPRESS_SCHEMAS', true),
|
||||
compressReminders: envFlag('COMPRESS_REMINDERS', true),
|
||||
compressToolResults: envFlag('COMPRESS_TOOL_RESULTS', true),
|
||||
// Variant C history-image: OFF by default. Round-3 spec marks the
|
||||
// savings as MARGINAL (~1% per-call) against HIGH cache-topology risk.
|
||||
// Flip via COMPRESS_HISTORY=1 or --history once telemetry shows the
|
||||
// static-slab cache_control still hits.
|
||||
compressHistory: envFlag('COMPRESS_HISTORY', false),
|
||||
historyKeepTail: Number(process.env.HISTORY_KEEP_TAIL ?? 4),
|
||||
historyMinPrefix: Number(process.env.HISTORY_MIN_PREFIX ?? 10),
|
||||
minCompressChars: Number(process.env.MIN_COMPRESS_CHARS ?? 2000),
|
||||
// Raised to 10,000 — the per-block break-even point at the current
|
||||
// renderer config (Unifont 10px, cell 5×11, 100 cols). The real gate
|
||||
// is `isCompressionProfitable()` in transform.ts; this is just a
|
||||
// fast-path skip for the obvious-no cases. Keep in sync with DEFAULTS.
|
||||
minReminderChars: Number(process.env.MIN_REMINDER_CHARS ?? 10000),
|
||||
minToolResultChars: Number(process.env.MIN_TOOL_RESULT_CHARS ?? 10000),
|
||||
cols: Number(process.env.COLS ?? 100),
|
||||
// R2 multi-column ON (2 cols) — single-col drops below break-even on
|
||||
// real tool-doc slabs. Override via MULTI_COL=1 or `--multi-col 1`.
|
||||
multiCol: Math.max(1, Number(process.env.MULTI_COL ?? 2) | 0),
|
||||
track: envFlag('PIXELPIPE_TRACK', true),
|
||||
eventsFile:
|
||||
process.env.PIXELPIPE_LOG ??
|
||||
path.join(os.homedir(), '.pixelpipe', 'events.jsonl'),
|
||||
};
|
||||
for (let i = 0; i < argv.length; i++) {
|
||||
const a = argv[i]!;
|
||||
const eat = () => argv[++i]!;
|
||||
switch (a) {
|
||||
case '-p':
|
||||
case '--port': o.port = Number(eat()); break;
|
||||
case '--upstream': o.upstream = eat(); break;
|
||||
case '--no-compress': o.compress = false; break;
|
||||
case '--no-tools': o.compressTools = false; break;
|
||||
case '--no-schemas': o.compressSchemas = false; break;
|
||||
case '--no-reminders': o.compressReminders = false; break;
|
||||
case '--no-tool-results':o.compressToolResults = false; break;
|
||||
case '--history': o.compressHistory = true; break;
|
||||
case '--no-history': o.compressHistory = false; break;
|
||||
case '--history-keep-tail': o.historyKeepTail = Number(eat()); break;
|
||||
case '--history-min-prefix': o.historyMinPrefix = Number(eat()); break;
|
||||
case '--min-chars': o.minCompressChars = Number(eat()); break;
|
||||
case '--min-reminder-chars': o.minReminderChars = Number(eat()); break;
|
||||
case '--min-tool-result-chars': o.minToolResultChars = Number(eat()); break;
|
||||
case '--cols': o.cols = Number(eat()); break;
|
||||
case '--multi-col': o.multiCol = Math.max(1, Number(eat()) | 0); break;
|
||||
case '--no-track': o.track = false; break;
|
||||
case '--events-file': o.eventsFile = eat(); break;
|
||||
case '-h':
|
||||
case '--help': printHelp(); process.exit(0);
|
||||
case '--version': printVersion(); process.exit(0);
|
||||
default:
|
||||
if (a.startsWith('--')) {
|
||||
console.error(`[pixelpipe] unknown option: ${a}`);
|
||||
process.exit(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
function printHelp(): void {
|
||||
console.log(`pixelpipe — token-saving proxy for Claude Code
|
||||
|
||||
Usage:
|
||||
pixelpipe [options] run the proxy
|
||||
pixelpipe run the proxy (no flags)
|
||||
|
||||
Stats, sessions, and cleanup tools all live in the live dashboard at
|
||||
The proxy always compresses tools, schemas, reminders, tool_results,
|
||||
and history; always tracks events to disk; and always measures real
|
||||
saved_pct via /v1/messages/count_tokens. Single codepath, no knobs.
|
||||
|
||||
Stats, sessions, and cleanup tools live in the dashboard at
|
||||
http://127.0.0.1:<port>/ (default port 47821)
|
||||
|
||||
Options:
|
||||
-p, --port <N> listen port (default 47821)
|
||||
--upstream <URL> Anthropic API base (default https://api.anthropic.com)
|
||||
--no-compress disable all compression (pure passthrough)
|
||||
--no-tools don't fold tool docs into the image
|
||||
--no-schemas don't include input_schema JSON in the image
|
||||
--no-reminders don't image-compress <system-reminder> blocks
|
||||
--no-tool-results don't image-compress large tool_result content
|
||||
--min-chars <N> skip system compression below this many chars (default 2000)
|
||||
--min-reminder-chars <N> per-block fast-skip threshold for reminders (default 10000)
|
||||
--min-tool-result-chars <N> per-block fast-skip threshold for tool_results (default 10000)
|
||||
--cols <N> soft-wrap column count (default 100)
|
||||
--multi-col <N> R2: pack N text columns per image (default 2;
|
||||
set to 1 to disable. Higher N may exceed the
|
||||
1568px image-width cap and gets clamped.)
|
||||
--no-track disable persistent event tracking
|
||||
--events-file <P> JSONL events path (default ~/.pixelpipe/events.jsonl)
|
||||
Flags:
|
||||
-h, --help show this help
|
||||
--version show version
|
||||
|
||||
Environment:
|
||||
Same as flags via PORT, ANTHROPIC_UPSTREAM, COMPRESS, COMPRESS_TOOLS,
|
||||
COMPRESS_SCHEMAS, COMPRESS_REMINDERS, COMPRESS_TOOL_RESULTS,
|
||||
MIN_COMPRESS_CHARS, MIN_REMINDER_CHARS, MIN_TOOL_RESULT_CHARS,
|
||||
COLS, MULTI_COL, PIXELPIPE_TRACK, PIXELPIPE_LOG.
|
||||
Environment (deployment-only):
|
||||
PORT listen port (default 47821)
|
||||
ANTHROPIC_UPSTREAM upstream API base (default https://api.anthropic.com)
|
||||
PIXELPIPE_LOG JSONL events path (default ~/.pixelpipe/events.jsonl)
|
||||
|
||||
Use with Claude Code:
|
||||
ANTHROPIC_BASE_URL=http://127.0.0.1:47821 claude
|
||||
|
||||
(pixelpipe now splits dynamic blocks itself, so the
|
||||
--exclude-dynamic-system-prompt-sections flag is no longer required.)
|
||||
`);
|
||||
}
|
||||
|
||||
@@ -479,22 +399,12 @@ async function main(): Promise<void> {
|
||||
// tools live in the dashboard (see http://127.0.0.1:${port}/).
|
||||
const argv = process.argv.slice(2);
|
||||
const opts = parseCli(argv);
|
||||
const transform: TransformOptions = {
|
||||
compress: opts.compress,
|
||||
compressTools: opts.compressTools,
|
||||
compressSchemas: opts.compressSchemas,
|
||||
compressReminders: opts.compressReminders,
|
||||
compressToolResults: opts.compressToolResults,
|
||||
compressHistory: opts.compressHistory,
|
||||
historyKeepTail: opts.historyKeepTail,
|
||||
historyMinPrefix: opts.historyMinPrefix,
|
||||
minCompressChars: opts.minCompressChars,
|
||||
minReminderChars: opts.minReminderChars,
|
||||
minToolResultChars: opts.minToolResultChars,
|
||||
cols: opts.cols,
|
||||
multiCol: opts.multiCol,
|
||||
};
|
||||
const tracker: Tracker = opts.track ? new FileTracker(opts.eventsFile) : noopTracker;
|
||||
// Transform options pass through empty — the proxy uses the DEFAULTS
|
||||
// baked into transform.ts (every compression on, history on, all
|
||||
// tuning parameters at their measured-best values). Per-request α
|
||||
// injection happens later via the function-form `transform` in
|
||||
// ProxyConfig so the gate gets the dashboard's live empirical rate.
|
||||
const tracker: Tracker = new FileTracker(opts.eventsFile);
|
||||
|
||||
// Sidecar dir for oversized 4xx request-body samples. Lives next to the
|
||||
// events.jsonl so a single `rm -rf` cleans up both. Lazy-mkdir'd on first
|
||||
@@ -515,20 +425,14 @@ async function main(): Promise<void> {
|
||||
|
||||
const config: ProxyConfig = {
|
||||
upstream: opts.upstream,
|
||||
// Resolve transform options per request so the live empirical
|
||||
// chars/token from dashboardState.fitCosts() flows into the
|
||||
// isCompressionProfitable gate. With α at the stale default of 0.25
|
||||
// (4 chars/tok), the gate rejects slabs as small as ~138K chars
|
||||
// because the row-aware image-count estimate exceeds the textual
|
||||
// token equivalence. Live α on Claude Code traffic runs ≈0.5-0.9
|
||||
// — once the fit has 3+ samples, the gate auto-tunes and accepts
|
||||
// compressions that the stale assumption was leaving on the table.
|
||||
// When fit is null, falls through to the baked-in DEFAULTS.charsPerToken.
|
||||
// Per-request transform options: inject the dashboard's live
|
||||
// empirical chars/token into the break-even gate when the regression
|
||||
// has converged. Everything else comes from DEFAULTS in transform.ts.
|
||||
transform: () => {
|
||||
const fit = dashboard.fitCosts();
|
||||
return fit && fit.chars_per_token > 0
|
||||
? { ...transform, charsPerToken: fit.chars_per_token }
|
||||
: transform;
|
||||
? { charsPerToken: fit.chars_per_token }
|
||||
: {};
|
||||
},
|
||||
onRequest: async (e) => {
|
||||
// Feed the dashboard BEFORE tracker.emit — toTrackEvent strips
|
||||
@@ -621,11 +525,7 @@ async function main(): Promise<void> {
|
||||
|
||||
server.listen(opts.port, () => {
|
||||
console.log(`[pixelpipe] listening on http://127.0.0.1:${opts.port} → ${opts.upstream}`);
|
||||
console.log(
|
||||
`[pixelpipe] config: compress=${opts.compress} tools=${opts.compressTools} schemas=${opts.compressSchemas} reminders=${opts.compressReminders} tool_results=${opts.compressToolResults} history=${opts.compressHistory} min=${opts.minCompressChars} cols=${opts.cols} multi_col=${opts.multiCol}`,
|
||||
);
|
||||
if (opts.track) console.log(`[pixelpipe] tracking events → ${opts.eventsFile}`);
|
||||
else console.log('[pixelpipe] tracking disabled (--no-track or PIXELPIPE_TRACK=0)');
|
||||
console.log(`[pixelpipe] tracking events → ${opts.eventsFile}`);
|
||||
console.log(`[pixelpipe] dashboard → http://127.0.0.1:${opts.port}/`);
|
||||
});
|
||||
|
||||
|
||||
+11
-7
@@ -374,19 +374,23 @@ describe('transformRequest + compressHistory', () => {
|
||||
);
|
||||
}
|
||||
|
||||
it('compressHistory:false (default) leaves messages array untouched', async () => {
|
||||
it('compressHistory is always-on by default — input msgs are not mutated', async () => {
|
||||
// The proxy ships with compressHistory baked in. This test pins the
|
||||
// INVARIANT that even when history compression is active, the
|
||||
// caller's `messages` array is not mutated in place — the function
|
||||
// returns a new body and leaves the input object identical to its
|
||||
// pre-call serialization.
|
||||
const msgs: Message[] = [];
|
||||
for (let i = 0; i < 12; i++) {
|
||||
msgs.push(i % 2 === 0 ? usr(bigPlain(3000)) : asst(bigPlain(3000)));
|
||||
}
|
||||
const before = JSON.stringify(msgs);
|
||||
const { body, info } = await transformRequest(mkBody(msgs, bigPlain(60_000)));
|
||||
expect(info.collapsedTurns).toBeUndefined();
|
||||
expect(info.historyReason).toBeUndefined();
|
||||
// The returned body's messages count equals the original
|
||||
const { body } = await transformRequest(mkBody(msgs, bigPlain(60_000)));
|
||||
// Input still byte-identical to its pre-call serialization.
|
||||
expect(JSON.stringify(msgs)).toBe(before);
|
||||
// And the returned body is valid JSON we can re-parse.
|
||||
const reparsed = JSON.parse(new TextDecoder().decode(body));
|
||||
expect(reparsed.messages.length).toBe(msgs.length);
|
||||
expect(JSON.stringify(msgs)).toBe(before); // input not mutated
|
||||
expect(Array.isArray(reparsed.messages)).toBe(true);
|
||||
});
|
||||
|
||||
it('compressHistory:true collapses an 8-closed + 2-live conversation', async () => {
|
||||
|
||||
+13
-8
@@ -166,18 +166,23 @@ test_port_in_use() {
|
||||
return 0
|
||||
}
|
||||
|
||||
# ---- Test 7: flag passthrough ------------------------------------------
|
||||
test_passthrough() {
|
||||
# ---- Test 7: unknown args are rejected ---------------------------------
|
||||
# The proxy takes no behavior flags; the restart script accepts only
|
||||
# --no-build. Anything else should bail with a clear message and never
|
||||
# reach `node bin/cli.js`.
|
||||
test_rejects_unknown_args() {
|
||||
local sandbox="$1" logf="$2"
|
||||
( cd "$REPO" && "$SCRIPT" --no-build --port 47899 --no-tools >/dev/null 2>&1 || true )
|
||||
grep -q "node bin/cli.js --port 47899 --no-tools" "$logf" || return 1
|
||||
if ( cd "$REPO" && "$SCRIPT" --no-build --port 47899 >/dev/null 2>&1 ); then
|
||||
return 1
|
||||
fi
|
||||
grep -q "node bin/cli.js" "$logf" && return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
run_test "no proxy running" test_no_running
|
||||
run_test "build failure aborts" test_build_failure
|
||||
run_test "port-in-use aborts" test_port_in_use
|
||||
run_test "flag passthrough" test_passthrough
|
||||
run_test "no proxy running" test_no_running
|
||||
run_test "build failure aborts" test_build_failure
|
||||
run_test "port-in-use aborts" test_port_in_use
|
||||
run_test "rejects unknown args" test_rejects_unknown_args
|
||||
|
||||
echo ""
|
||||
echo "$PASS passed, $FAIL failed"
|
||||
|
||||
Reference in New Issue
Block a user