Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ Versions follow the merge of a `*_release-v*` branch; CI publishes to npm on tag

## [Unreleased]

### Changes

- **Historical-image strip now uses the acp-kernel wire primitive (#617)**: the #618 opt-in strip (`compress.stripImages` / `compress.stripImagesKeepRecent`) was implemented locally (`src/strip-images.ts`, a per-protocol traversal mirroring `src/image-tokens.ts`). With the mechanism now sunk into acp-kernel's wire layer (kernel #215, shipped 0.0.58; this repo pins 0.0.59), the proxy calls `stripHistoricalImages` straight from `acp-kernel/wire` — identical semantics (recent-N kept, image-only messages collapse to a `[image]` placeholder, identity no-op) and a single source of truth shared with the pi/omp adapters (billion-context-pi#321). Local module deleted; only the host-side policy (config keys + default-5 constant) remains, moved to `src/compress-settings.ts`. No behavior change.

### Fixes

- **Lenient compress-arg parsing: salvage single-quoted JSON before hard rejection (#603)**: weak local models (reported via omp#121) emit `compress` args with single quotes (`{'content':[{'startId':...}]}`) — a malformation class the kernel's salvage ladder (fences, trailing commas, raw newlines, double-stringification, truncated/prose-wrapped arrays) does not cover, so the whole call was rejected `kind=malformed-json`, the round was wasted, and the model saw a FAILED result that can trigger tag-echoing. `parseCompressInput` now retries once through a quote-normalization pass when the kernel recovers zero ranges or reports invalid items: a state machine converts single-quoted strings to double-quoted ones (apostrophes inside double-quoted values are data and are copied verbatim; control characters inside single-quoted regions become JSON escapes), applied to raw-string args and to object inputs whose `content` value is a stringified array. The retry wins only when it recovers strictly more ranges — valid input is never rewritten — and salvaged ranges pass the same ref-validation gate as any other range, so the worst case is a wasted round, never a wrong compression. A `[acp-compress-input] quote-salvage: recovered N range(s)` warn logs each recovery for attribution.
Expand Down
6 changes: 6 additions & 0 deletions src/compress-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ import { DEFAULT_ABSORB_CONFIG, defaultPrompts, resolvePrompts, type AbsorbConfi
import { findRoute, type CompressSettings, type ProviderRoutes } from "./config.js";
import { log as loggerLog } from "./logger.js";

/** Host-side policy default for `compress.stripImagesKeepRecent` (#617): how
* many of the most recent messages keep their image payloads when stripping
* is enabled. The strip mechanism itself lives in acp-kernel's wire layer
* (kernel #215) — only the opt-in policy stays host-side. */
export const DEFAULT_STRIP_IMAGES_KEEP_RECENT = 5;

/** Resolve a raw `contextLimit` value to an absolute token count.
* - `number` → used as-is (absolute window).
* - `string` ending in `%` (e.g. `"70%"`) → that fraction of `nativeLimit`.
Expand Down
12 changes: 7 additions & 5 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,11 +151,13 @@ export type CompressSettings = {
toolName?: string;
};

/** Opt-in removal of historical image payloads (src/strip-images.ts). When
* true, every message except the most recent {@link stripImagesKeepRecent}
* has its image parts dropped before the wire rebuild (image-only content
* collapses to an "[image]" placeholder). Off by default — the #488 image
* floor / overflow 502 stays the opt-in signal until this is enabled. */
/** Opt-in removal of historical image payloads, executed by the kernel's
* wire-layer primitive `stripHistoricalImages` from "acp-kernel/wire"
* (kernel #215; host-side policy only). When true, every message except
* the most recent {@link stripImagesKeepRecent} has its image parts dropped
* before the wire rebuild (image-only content collapses to an "[image]"
* placeholder). Off by default — the #488 image floor / overflow 502 stays
* the opt-in signal until this is enabled. */
stripImages?: boolean;
/** With {@link stripImages}, how many trailing messages keep their images
* verbatim (default 5). Ignored unless stripImages is true. */
Expand Down
9 changes: 5 additions & 4 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import http from "node:http";
import fs from "node:fs";
import { randomUUID } from "node:crypto";
import { createCore, type CompressionCore, type CompressionState, type Config, type CoreMessage, type NudgeDecision, type Prompts, defaultPrompts, defaultCountTokens, estimateTokensFast, renderNudgeText, deactivateBlock, viableRanges } from "acp-kernel";
import { resolveCompress, resolveCompressPrompts, resolveRequestConfig } from "./compress-settings.js";
import { DEFAULT_STRIP_IMAGES_KEEP_RECENT, stripHistoricalImages } from "./strip-images.js";
import { DEFAULT_STRIP_IMAGES_KEEP_RECENT, resolveCompress, resolveCompressPrompts, resolveRequestConfig } from "./compress-settings.js";
import type { ProxyOptions } from "./config.js";
import { loadOptions, loadRoutes } from "./config.js";
import { resetProxyCache } from "./upstream-proxy.js";
Expand All @@ -13,14 +12,16 @@ import { codexAlignedWindow } from "./codex-models.js";
import { fetchWithTimeout, MAX_REQUEST_BYTES } from "./fetch-util.js";
import { formatUpstreamError, getUpstreamConnectionStatus, recordUpstreamConnection, resolveProxy, resolveProxyDecision, proxyDispatcher, type UpstreamProxyDecision } from "./upstream-proxy.js";
import { maskHeaderForLog, maskHeadersForLog, maskHostPortForLog, maskUrlForLog, maskUrlsInText } from "./log-mask.js";
// Protocol codecs live in the kernel now (single source of truth shared with
// the omp/pi adapters): import from "acp-kernel/wire".
// Protocol codecs + the historical-image strip primitive live in the kernel now
// (single source of truth shared with the omp/pi adapters): import from
// "acp-kernel/wire" (kernel #215).
import {
anthropicToCore,
coreToAnthropic,
conversationSignalAnthropic,
extractSystem,
buildSystem,
stripHistoricalImages,
type AnthropicRequestBody,
} from "acp-kernel/wire";
import {
Expand Down
93 changes: 0 additions & 93 deletions src/strip-images.ts

This file was deleted.

5 changes: 4 additions & 1 deletion tests/strip-images.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import test from "node:test";
import assert from "node:assert/strict";
import { DEFAULT_STRIP_IMAGES_KEEP_RECENT, stripHistoricalImages } from "../src/strip-images.ts";
// The mechanism moved to acp-kernel's wire layer (kernel #215); these tests
// stay as host-level regression coverage over the bundled kernel export.
import { stripHistoricalImages } from "acp-kernel/wire";
import { DEFAULT_STRIP_IMAGES_KEEP_RECENT } from "../src/compress-settings.ts";

test("default keep-recent constant is 5", () => {
assert.equal(DEFAULT_STRIP_IMAGES_KEEP_RECENT, 5);
Expand Down
Loading