From e2f74ef11ae43aebaf96bb19144cf529e094e129 Mon Sep 17 00:00:00 2001 From: Arseny Kravchenko Date: Fri, 10 Jul 2026 09:54:33 +0200 Subject: [PATCH 01/18] bench(proxy): add TOON kernel and streaming replay baseline benchmarks Claude-Session: https://claude.ai/code/session_01QkeLJprfeq75d2NY39aFiu --- .../proxy/__bench__/bench-concurrency.ts | 91 +++++++++ .../src/routes/proxy/__bench__/bench-env.ts | 10 + .../proxy/__bench__/bench-stream-replay.ts | 120 ++++++++++++ .../proxy/__bench__/bench-toon-kernel.ts | 72 ++++++++ .../src/routes/proxy/__bench__/bench-util.ts | 25 +++ .../src/routes/proxy/__bench__/corpus.ts | 172 ++++++++++++++++++ .../proxy/__bench__/toon-kernel-reference.ts | 38 ++++ .../proxy/__bench__/validate-reference.ts | 128 +++++++++++++ 8 files changed, 656 insertions(+) create mode 100644 platform/backend/src/routes/proxy/__bench__/bench-concurrency.ts create mode 100644 platform/backend/src/routes/proxy/__bench__/bench-env.ts create mode 100644 platform/backend/src/routes/proxy/__bench__/bench-stream-replay.ts create mode 100644 platform/backend/src/routes/proxy/__bench__/bench-toon-kernel.ts create mode 100644 platform/backend/src/routes/proxy/__bench__/bench-util.ts create mode 100644 platform/backend/src/routes/proxy/__bench__/corpus.ts create mode 100644 platform/backend/src/routes/proxy/__bench__/toon-kernel-reference.ts create mode 100644 platform/backend/src/routes/proxy/__bench__/validate-reference.ts diff --git a/platform/backend/src/routes/proxy/__bench__/bench-concurrency.ts b/platform/backend/src/routes/proxy/__bench__/bench-concurrency.ts new file mode 100644 index 00000000000..c43e24df6e3 --- /dev/null +++ b/platform/backend/src/routes/proxy/__bench__/bench-concurrency.ts @@ -0,0 +1,91 @@ +/** + * Benchmark (c): concurrency guardrail scenario. + * + * Runs 8 concurrent async batches of the TOON kernel (mixed 1KB-5MB items, + * ~8.8MB per batch, ~70MB total) with an event-loop yield between items, + * and reports p50/p99/max event-loop delay plus peak RSS. + * + * Run from platform/backend: + * pnpm exec tsx src/routes/proxy/__bench__/bench-concurrency.ts + */ +import { monitorEventLoopDelay, performance } from "node:perf_hooks"; +import { setImmediate as yieldEventLoop } from "node:timers/promises"; +import { fmt } from "./bench-util"; +import { batchBytes, buildBatch, type CorpusSpec } from "./corpus"; +import { + encodeToolResultsReference, + type ToonKernelItem, +} from "./toon-kernel-reference"; + +const CONCURRENCY = 8; + +const WORKER_PARTS: CorpusSpec[] = [ + { name: "w-5MB", payloadBytes: 5 << 20, count: 1 }, + { name: "w-1MB", payloadBytes: 1 << 20, count: 2 }, + { name: "w-100KB", payloadBytes: 100 << 10, count: 10 }, + { name: "w-10KB", payloadBytes: 10 << 10, count: 20 }, + { name: "w-1KB", payloadBytes: 1 << 10, count: 50 }, +]; + +function buildWorkerBatch(seed: number): ToonKernelItem[] { + return WORKER_PARTS.flatMap((part, i) => buildBatch(part, seed + i * 31)); +} + +// Prevents dead-code elimination of the encode results. +let sink = 0; +let peakRss = 0; + +function sampleRss(): void { + const rss = process.memoryUsage().rss; + if (rss > peakRss) { + peakRss = rss; + } +} + +async function worker(items: ToonKernelItem[]): Promise { + for (const item of items) { + const [result] = encodeToolResultsReference([item]); + sink += result.encoded === null ? 0 : result.encoded.length; + sampleRss(); + await yieldEventLoop(); + } +} + +async function main(): Promise { + const batches: ToonKernelItem[][] = []; + for (let i = 0; i < CONCURRENCY; i++) { + batches.push(buildWorkerBatch(1000 + i)); + } + const totalMB = + batches.reduce((sum, b) => sum + batchBytes(b), 0) / (1 << 20); + const baselineRssMB = process.memoryUsage().rss / (1 << 20); + + const histogram = monitorEventLoopDelay({ resolution: 10 }); + const rssTimer = setInterval(sampleRss, 25); + histogram.enable(); + const start = performance.now(); + await Promise.all(batches.map((b) => worker(b))); + const wallMs = performance.now() - start; + histogram.disable(); + clearInterval(rssTimer); + sampleRss(); + + const toMs = (ns: number) => ns / 1e6; + console.info( + "bench-concurrency: 8 concurrent TOON kernel batches (baseline)", + ); + console.info( + [ + `total=${fmt(totalMB, 1)}MB`, + `wall=${fmt(wallMs)}ms`, + `elDelay p50=${fmt(toMs(histogram.percentile(50)))}ms`, + `p99=${fmt(toMs(histogram.percentile(99)))}ms`, + `max=${fmt(toMs(histogram.max))}ms`, + `rss baseline=${fmt(baselineRssMB, 1)}MB`, + `peak=${fmt(peakRss / (1 << 20), 1)}MB`, + ].join(" "), + ); + console.info(`(sink=${sink})`); +} + +main(); diff --git a/platform/backend/src/routes/proxy/__bench__/bench-env.ts b/platform/backend/src/routes/proxy/__bench__/bench-env.ts new file mode 100644 index 00000000000..9e2e4daf83c --- /dev/null +++ b/platform/backend/src/routes/proxy/__bench__/bench-env.ts @@ -0,0 +1,10 @@ +/** + * Side-effect module for the T0 benchmark harness. Import it FIRST in any + * bench script that pulls in the backend module graph: `@/config` requires a + * database URL at import time. The URL below is never dialed by the + * benchmarks — it only satisfies config validation. + */ +process.env.ARCHESTRA_DATABASE_URL ??= + "postgres://bench:bench@127.0.0.1:5432/bench"; +// Quiet per-message info logs from the adapter path during runs. +process.env.ARCHESTRA_LOGGING_LEVEL ??= "warn"; diff --git a/platform/backend/src/routes/proxy/__bench__/bench-stream-replay.ts b/platform/backend/src/routes/proxy/__bench__/bench-stream-replay.ts new file mode 100644 index 00000000000..5093f66e44a --- /dev/null +++ b/platform/backend/src/routes/proxy/__bench__/bench-stream-replay.ts @@ -0,0 +1,120 @@ +/** + * Benchmark (b): streaming tool-call replay quadratic path. + * + * Feeds OpenAIStreamAdapter a synthetic stream of tool-call argument + * fragments and, after every fragment chunk, calls getRawToolCallEvents() + * plus the handler's written-index dedup loop — mimicking the per-chunk + * non-blocking-policy path at llm-proxy-handler.ts:1214-1221. The + * re-serialization of the full event history on every call is the O(k^2) + * term this baseline pins down. + * + * Run from platform/backend: + * pnpm exec tsx src/routes/proxy/__bench__/bench-stream-replay.ts + */ +import "./bench-env"; +import { performance } from "node:perf_hooks"; +import type { OpenAi } from "@/types"; +import { OpenAIStreamAdapter } from "../adapters/openai"; +import { fmt, summarize } from "./bench-util"; + +type Chunk = OpenAi.Types.ChatCompletionChunk; + +const FRAGMENT_COUNTS = [100, 500, 1000, 2000]; +const REPEATS = 5; +const FRAGMENT = '{"query":"synthetic fragment payload #'; + +function makeChunk(fragmentIndex: number): Chunk { + const first = fragmentIndex === 0; + return { + id: "chatcmpl-bench", + object: "chat.completion.chunk", + created: 1_700_000_000, + model: "gpt-bench", + choices: [ + { + index: 0, + delta: { + tool_calls: [ + first + ? { + index: 0, + id: "call_bench_0", + type: "function", + function: { name: "search_documents", arguments: "" }, + } + : { + index: 0, + function: { arguments: `${FRAGMENT}${fragmentIndex}"}` }, + }, + ], + }, + finish_reason: null, + logprobs: null, + }, + ], + }; +} + +// Prevents dead-code elimination of the replayed SSE strings. +let sink = 0; + +function runScenario(fragmentCount: number): number { + const adapter = new OpenAIStreamAdapter("openai"); + const streamedEventIndices = new Set(); + const chunks: Chunk[] = []; + for (let i = 0; i < fragmentCount; i++) { + chunks.push(makeChunk(i)); + } + + const start = performance.now(); + for (const chunk of chunks) { + const result = adapter.processChunk(chunk); + if (result.isToolCallChunk) { + // Handler's per-chunk replay + dedup (llm-proxy-handler.ts:1214-1221). + const allEvents = adapter.getRawToolCallEvents(); + for (let i = 0; i < allEvents.length; i++) { + if (!streamedEventIndices.has(i)) { + sink += allEvents[i].length; + streamedEventIndices.add(i); + } + } + } + } + return performance.now() - start; +} + +console.info( + "bench-stream-replay: per-chunk getRawToolCallEvents replay (baseline)", +); +const perCount = new Map(); +for (const fragmentCount of FRAGMENT_COUNTS) { + runScenario(fragmentCount); // warmup + const samples: number[] = []; + for (let r = 0; r < REPEATS; r++) { + samples.push(runScenario(fragmentCount)); + } + const s = summarize(samples); + perCount.set(fragmentCount, s.meanMs); + console.info( + [ + `fragments=${String(fragmentCount).padStart(5)}`, + `mean=${fmt(s.meanMs).padStart(9)}ms`, + `stdev=${fmt(s.stdevMs).padStart(7)}ms`, + `min=${fmt(s.minMs).padStart(9)}ms`, + `perChunk=${fmt(s.meanMs / fragmentCount, 4).padStart(8)}ms`, + ].join(" "), + ); +} + +const base = perCount.get(FRAGMENT_COUNTS[0]); +if (base !== undefined && base > 0) { + for (const fragmentCount of FRAGMENT_COUNTS.slice(1)) { + const ratio = (perCount.get(fragmentCount) ?? 0) / base; + const kRatio = fragmentCount / FRAGMENT_COUNTS[0]; + console.info( + `scaling ${FRAGMENT_COUNTS[0]} -> ${fragmentCount}: time x${fmt(ratio, 1)} ` + + `(linear would be x${fmt(kRatio, 0)}, quadratic x${fmt(kRatio ** 2, 0)})`, + ); + } +} +console.info(`(sink=${sink})`); diff --git a/platform/backend/src/routes/proxy/__bench__/bench-toon-kernel.ts b/platform/backend/src/routes/proxy/__bench__/bench-toon-kernel.ts new file mode 100644 index 00000000000..e02d771ca7d --- /dev/null +++ b/platform/backend/src/routes/proxy/__bench__/bench-toon-kernel.ts @@ -0,0 +1,72 @@ +/** + * Benchmark (a): TOON kernel (unwrap -> JSON.parse -> toonEncode) via the TS + * reference backend, over deterministic synthetic corpora. + * + * Run from platform/backend: + * pnpm exec tsx src/routes/proxy/__bench__/bench-toon-kernel.ts + */ +import { performance } from "node:perf_hooks"; +import { fmt, summarize } from "./bench-util"; +import { + batchBytes, + buildBatch, + buildJumboBatch, + CORPUS_SPECS, +} from "./corpus"; +import { + encodeToolResultsReference, + type ToonKernelItem, +} from "./toon-kernel-reference"; + +const TIME_BUDGET_MS = 4_000; +const MIN_ITERATIONS = 5; +const MAX_ITERATIONS = 200; + +// Prevents dead-code elimination of the encode results. +let sink = 0; + +function runBatch(items: ToonKernelItem[]): number { + const start = performance.now(); + const results = encodeToolResultsReference(items); + const elapsed = performance.now() - start; + for (const r of results) { + sink += r.encoded === null ? r.normalized.length : r.encoded.length; + } + return elapsed; +} + +function benchCorpus(name: string, items: ToonKernelItem[]): void { + const totalMB = batchBytes(items) / (1 << 20); + runBatch(items); // warmup + const samples: number[] = []; + const budgetStart = performance.now(); + while ( + samples.length < MAX_ITERATIONS && + (samples.length < MIN_ITERATIONS || + performance.now() - budgetStart < TIME_BUDGET_MS) + ) { + samples.push(runBatch(items)); + } + const s = summarize(samples); + const mbPerSec = totalMB / (s.meanMs / 1000); + console.info( + [ + name.padEnd(6), + `items=${String(items.length).padStart(4)}`, + `total=${fmt(totalMB, 1).padStart(6)}MB`, + `iters=${String(s.iterations).padStart(3)}`, + `mean=${fmt(s.meanMs).padStart(9)}ms/batch`, + `stdev=${fmt(s.stdevMs).padStart(7)}ms`, + `min=${fmt(s.minMs).padStart(9)}ms`, + `throughput=${fmt(mbPerSec, 1).padStart(7)}MB/s`, + `perItem=${fmt(s.meanMs / items.length, 3).padStart(9)}ms`, + ].join(" "), + ); +} + +console.info("bench-toon-kernel: TS reference backend (baseline)"); +for (const spec of CORPUS_SPECS) { + benchCorpus(spec.name, buildBatch(spec, 42)); +} +benchCorpus("70MB", buildJumboBatch(4242)); +console.info(`(sink=${sink})`); diff --git a/platform/backend/src/routes/proxy/__bench__/bench-util.ts b/platform/backend/src/routes/proxy/__bench__/bench-util.ts new file mode 100644 index 00000000000..7db8886e425 --- /dev/null +++ b/platform/backend/src/routes/proxy/__bench__/bench-util.ts @@ -0,0 +1,25 @@ +export interface SampleStats { + iterations: number; + meanMs: number; + stdevMs: number; + minMs: number; + maxMs: number; +} + +export function summarize(samplesMs: number[]): SampleStats { + const n = samplesMs.length; + const meanMs = samplesMs.reduce((a, b) => a + b, 0) / n; + const variance = + n > 1 ? samplesMs.reduce((a, b) => a + (b - meanMs) ** 2, 0) / (n - 1) : 0; + return { + iterations: n, + meanMs, + stdevMs: Math.sqrt(variance), + minMs: Math.min(...samplesMs), + maxMs: Math.max(...samplesMs), + }; +} + +export function fmt(value: number, digits = 2): string { + return value.toFixed(digits); +} diff --git a/platform/backend/src/routes/proxy/__bench__/corpus.ts b/platform/backend/src/routes/proxy/__bench__/corpus.ts new file mode 100644 index 00000000000..71d37f67b1d --- /dev/null +++ b/platform/backend/src/routes/proxy/__bench__/corpus.ts @@ -0,0 +1,172 @@ +import type { ToonKernelItem } from "./toon-kernel-reference"; + +/** + * Deterministic synthetic corpora for the TOON kernel benchmarks (T0). + * + * Every batch is fully reproducible from its seed. Item mix per 10 items: + * 6 uniform JSON arrays of objects, 2 non-array JSON objects, 2 non-JSON + * prose strings; 2 of the JSON items are wrapped in the n8n/Vercel-style + * `[{"type":"text","text":...}]` wrapper (exercising unwrapToolContent), + * and roughly 1 in 7 unwrapped items uses `unwrap: false` (Bedrock-style). + */ + +export interface CorpusSpec { + name: string; + payloadBytes: number; + count: number; +} + +export const CORPUS_SPECS: CorpusSpec[] = [ + { name: "1KB", payloadBytes: 1 << 10, count: 256 }, + { name: "10KB", payloadBytes: 10 << 10, count: 128 }, + { name: "100KB", payloadBytes: 100 << 10, count: 64 }, + { name: "1MB", payloadBytes: 1 << 20, count: 16 }, + { name: "5MB", payloadBytes: 5 << 20, count: 8 }, +]; + +export function buildBatch(spec: CorpusSpec, seed: number): ToonKernelItem[] { + const rng = mulberry32(seed); + const items: ToonKernelItem[] = []; + for (let i = 0; i < spec.count; i++) { + const r = i % 10; + const kind: PayloadKind = + r === 2 || r === 7 ? "object" : r === 4 || r === 9 ? "nonjson" : "array"; + const wrapped = kind !== "nonjson" && (r === 6 || r === 7); + let payload = buildPayload(kind, spec.payloadBytes, rng); + if (wrapped) { + payload = JSON.stringify([{ type: "text", text: payload }]); + } + // Wrapped payloads must be unwrapped to reach the inner JSON; a slice of + // plain payloads mimics the Bedrock branches, which never unwrap. + const unwrap = wrapped ? true : i % 7 !== 3; + items.push({ rawContent: payload, unwrap }); + } + return items; +} + +/** Mixed-size ~70MB batch approximating the proxy body limit. */ +export function buildJumboBatch(seed: number): ToonKernelItem[] { + const parts: CorpusSpec[] = [ + { name: "jumbo-5MB", payloadBytes: 5 << 20, count: 8 }, // ~40MB + { name: "jumbo-1MB", payloadBytes: 1 << 20, count: 16 }, // ~16MB + { name: "jumbo-100KB", payloadBytes: 100 << 10, count: 96 }, // ~9.4MB + { name: "jumbo-10KB", payloadBytes: 10 << 10, count: 300 }, // ~2.9MB + { name: "jumbo-1KB", payloadBytes: 1 << 10, count: 1024 }, // ~1MB + ]; + return parts.flatMap((part, i) => buildBatch(part, seed + i * 101)); +} + +export function batchBytes(items: ToonKernelItem[]): number { + return items.reduce((sum, item) => sum + item.rawContent.length, 0); +} + +// ============================================================================= +// INTERNALS +// ============================================================================= + +type PayloadKind = "array" | "object" | "nonjson"; + +function mulberry32(seed: number): () => number { + let a = seed >>> 0; + return () => { + a = (a + 0x6d2b79f5) | 0; + let t = Math.imul(a ^ (a >>> 15), 1 | a); + t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; + return ((t ^ (t >>> 14)) >>> 0) / 4294967296; + }; +} + +const WORDS = [ + "alpha", + "bravo", + "charlie", + "delta", + "echo", + "foxtrot", + "golf", + "hotel", + "india", + "juliet", + "kilo", + "lima", + "mike", + "november", + "oscar", + "papa", + "quebec", + "romeo", + "sierra", + "tango", + "uniform", + "victor", + "whiskey", + "zulu", +]; + +const STATUSES = ["active", "pending", "archived", "failed"]; + +function pick(rng: () => number, pool: T[]): T { + return pool[Math.floor(rng() * pool.length)]; +} + +function makeRow(rng: () => number, id: number): Record { + return { + id, + sku: `SKU-${Math.floor(rng() * 1_000_000)}`, + name: `${pick(rng, WORDS)} ${pick(rng, WORDS)}`, + status: pick(rng, STATUSES), + score: Math.round(rng() * 10_000) / 100, + quantity: Math.floor(rng() * 500), + active: rng() > 0.5, + updatedAt: `2026-0${1 + Math.floor(rng() * 6)}-${String(1 + Math.floor(rng() * 28)).padStart(2, "0")}T12:00:00Z`, + }; +} + +function buildPayload( + kind: PayloadKind, + targetBytes: number, + rng: () => number, +): string { + switch (kind) { + case "array": { + const rows: Record[] = []; + let size = 2; // brackets + let id = 0; + while (size < targetBytes) { + const row = makeRow(rng, id++); + size += JSON.stringify(row).length + 1; + rows.push(row); + } + return JSON.stringify(rows); + } + case "object": { + const entries: Record = {}; + let size = 64; + let id = 0; + while (size < targetBytes) { + const key = `entry_${id}`; + const value = { + ...makeRow(rng, id), + nested: { tags: [pick(rng, WORDS), pick(rng, WORDS)], depth: 2 }, + }; + size += JSON.stringify(value).length + key.length + 4; + entries[key] = value; + id++; + } + return JSON.stringify({ + meta: { source: "bench", version: 3, total: id }, + entries, + }); + } + case "nonjson": { + const parts: string[] = [`Tool run ${Math.floor(rng() * 1000)} output:`]; + let size = parts[0].length; + while (size < targetBytes) { + const word = pick(rng, WORDS); + parts.push(word); + size += word.length + 1; + } + return parts.join(" "); + } + } +} diff --git a/platform/backend/src/routes/proxy/__bench__/toon-kernel-reference.ts b/platform/backend/src/routes/proxy/__bench__/toon-kernel-reference.ts new file mode 100644 index 00000000000..d1b85f19c95 --- /dev/null +++ b/platform/backend/src/routes/proxy/__bench__/toon-kernel-reference.ts @@ -0,0 +1,38 @@ +import { encode as toonEncode } from "@toon-format/toon"; +import { unwrapToolContent } from "../utils/unwrap-tool-content"; + +export interface ToonKernelItem { + rawContent: string; + unwrap: boolean; +} + +export interface ToonKernelResult { + normalized: string; + encoded: string | null; +} + +/** + * TS reference backend for the planned native kernel boundary: + * batch of { rawContent, unwrap } -> { normalized, encoded }. + * + * Mirrors the per-item pipeline of convertToolResultsToToon + * (../adapters/openai.ts:1261+): unwrapToolContent -> JSON.parse -> + * toonEncode, yielding `encoded: null` when the content is not parseable + * JSON (the adapter then keeps the original content). The double parse + * (inside unwrapToolContent and again here) is deliberate — it is what the + * production path pays today. Output equivalence with the real adapter path + * is checked by validate-reference.ts. + */ +export function encodeToolResultsReference( + items: ToonKernelItem[], +): ToonKernelResult[] { + return items.map(({ rawContent, unwrap }) => { + const normalized = unwrap ? unwrapToolContent(rawContent) : rawContent; + try { + const parsed = JSON.parse(normalized); + return { normalized, encoded: toonEncode(parsed) }; + } catch { + return { normalized, encoded: null }; + } + }); +} diff --git a/platform/backend/src/routes/proxy/__bench__/validate-reference.ts b/platform/backend/src/routes/proxy/__bench__/validate-reference.ts new file mode 100644 index 00000000000..8b084d962d8 --- /dev/null +++ b/platform/backend/src/routes/proxy/__bench__/validate-reference.ts @@ -0,0 +1,128 @@ +/** + * One-off assertion script (not a permanent test): validates that the bench + * harness's TS reference backend produces byte-identical transformed content + * to the real adapter path (convertToolResultsToToon in ../adapters/openai.ts) + * on a set of fixtures, including the tokenizer keep/reject decision. + * + * Run from platform/backend: + * pnpm exec tsx src/routes/proxy/__bench__/validate-reference.ts + */ +import "./bench-env"; +import assert from "node:assert/strict"; +import { ModelModel } from "@/models"; +import { getTokenizer } from "@/tokenizers"; +import type { OpenAi } from "@/types"; +import { convertToolResultsToToon } from "../adapters/openai"; +import { encodeToolResultsReference } from "./toon-kernel-reference"; + +// True process boundary (Postgres). Pricing lookup is irrelevant to the +// transformation output being validated, and this throwaway script runs +// without a database. +ModelModel.calculateCostSavings = async () => 0; + +const uniformArray = JSON.stringify( + Array.from({ length: 50 }, (_, i) => ({ + id: i, + name: `item ${i}`, + status: i % 2 === 0 ? "active" : "archived", + score: i * 1.5, + })), +); + +const FIXTURES: string[] = [ + // Uniform array of objects — TOON compression expected to win. + uniformArray, + // n8n/Vercel-style text-block wrapper around JSON. + JSON.stringify([{ type: "text", text: uniformArray }]), + // Multi-element wrapper — pins the first-text-element-only behavior. + JSON.stringify([ + { type: "text", text: uniformArray }, + { type: "text", text: '{"ignored":true}' }, + ]), + // Non-array JSON object root. + JSON.stringify({ + meta: { total: 3, source: "db" }, + rows: [ + { id: 1, value: "a" }, + { id: 2, value: "b" }, + { id: 3, value: "c" }, + ], + }), + // Non-JSON prose — adapter must keep it untouched. + "Command failed: ENOENT no such file or directory, open '/tmp/x'", + // Escaping / unicode / nesting / boundary-ish numbers. + JSON.stringify({ + text: 'line1\nline2\t"quoted" \\ back', + emoji: "héllo wörld ✓ 日本語", + nested: { deep: { deeper: [1, 2, { x: null }] } }, + numbers: [0, -0, 1e21, 9007199254740991, 0.1], + }), + // Tiny payload where compression may or may not win — decision replicated. + '{"a":1}', +]; + +async function main(): Promise { + const tokenizer = getTokenizer("openai"); + const messages: OpenAi.Types.ChatCompletionsRequest["messages"] = [ + { role: "user", content: "run the tools" }, + ...FIXTURES.map( + (content, i) => + ({ + role: "tool", + tool_call_id: `call_${i}`, + content, + }) as const, + ), + ]; + + const { messages: transformed, stats } = await convertToolResultsToToon( + messages, + "gpt-4o", + "openai", + ); + + let compressedCount = 0; + FIXTURES.forEach((raw, i) => { + const actual = transformed[i + 1]; + assert.equal(actual.role, "tool"); + + // Reference backend, then the adapter's tokenizer keep/reject decision + // replicated on top of it (openai.ts:1292-1305). + const [ref] = encodeToolResultsReference([ + { rawContent: raw, unwrap: true }, + ]); + let expected = raw; + if (ref.encoded !== null) { + const tokensBefore = tokenizer.countTokens([ + { role: "user", content: ref.normalized }, + ]); + const tokensAfter = tokenizer.countTokens([ + { role: "user", content: ref.encoded }, + ]); + if (tokensAfter < tokensBefore) { + expected = ref.encoded; + compressedCount++; + } + } + assert.equal( + actual.content, + expected, + `fixture ${i}: adapter output differs from reference backend`, + ); + }); + + // Guard against a vacuous pass: the encode path must actually fire. + assert.ok( + compressedCount >= 3, + `expected >=3 fixtures to compress, got ${compressedCount}`, + ); + assert.ok(stats.hadToolResults); + assert.ok(stats.wasEffective); + + console.info( + `validate-reference: OK (${FIXTURES.length} fixtures, ${compressedCount} compressed, ` + + `tokensBefore=${stats.tokensBefore}, tokensAfter=${stats.tokensAfter})`, + ); +} + +main(); From 8bd4c02dc76aef9f5d62928a83561d2bc81a11a3 Mon Sep 17 00:00:00 2001 From: Arseny Kravchenko Date: Fri, 10 Jul 2026 10:29:55 +0200 Subject: [PATCH 02/18] fix(proxy): O(k^2) tool-call SSE replay and wrapper event loss Memoize serialized SSE per event at accumulation time in the openai, anthropic, and bedrock stream adapters (2000-fragment replay: 1220ms -> 9.2ms) and document the getRawToolCallEvents contract: full stable history, index-stable, non-destructive. Make the anthropic-openai wrapper conform (append-only instead of splice(0)): previously the handler's index-based dedup dropped every tool-call argument delta on the model-router Anthropic path (streamed AND buffered-approved) - clients got tool names with empty arguments. Exact event-sequence tests pin all four paths x three policy scenarios; native-path bytes are unchanged. The cohere-openai wrapper is documented as known-non-conforming (never translates tool events; pre-existing, tracked separately). Claude-Session: https://claude.ai/code/session_01QkeLJprfeq75d2NY39aFiu --- .../routes/proxy/adapters/anthropic-openai.ts | 7 +- .../src/routes/proxy/adapters/anthropic.ts | 22 +- .../src/routes/proxy/adapters/bedrock.ts | 128 ++-- .../routes/proxy/adapters/cohere-openai.ts | 6 + .../src/routes/proxy/adapters/openai.ts | 11 +- .../proxy/adapters/stream-replay.test.ts | 678 ++++++++++++++++++ .../routes/proxy/routes/model-router.test.ts | 119 +++ platform/backend/src/types/llm-provider.ts | 18 +- 8 files changed, 896 insertions(+), 93 deletions(-) create mode 100644 platform/backend/src/routes/proxy/adapters/stream-replay.test.ts diff --git a/platform/backend/src/routes/proxy/adapters/anthropic-openai.ts b/platform/backend/src/routes/proxy/adapters/anthropic-openai.ts index 1877e08acfb..9a251886b05 100644 --- a/platform/backend/src/routes/proxy/adapters/anthropic-openai.ts +++ b/platform/backend/src/routes/proxy/adapters/anthropic-openai.ts @@ -159,7 +159,12 @@ class AnthropicOpenaiStreamAdapter } getRawToolCallEvents(): string[] { - return this.pendingToolCallEvents.splice(0); + // Full append-only history, per the LLMStreamAdapter contract: the handler + // dedups replayed events by array index, so draining the buffer here + // (as an earlier splice(0) implementation did) re-based indices at 0 and + // made the handler drop every tool-call argument delta after the first + // event — and flush nothing at all for buffered-then-approved tool calls. + return this.pendingToolCallEvents; } formatCompleteTextSSE(text: string): string[] { diff --git a/platform/backend/src/routes/proxy/adapters/anthropic.ts b/platform/backend/src/routes/proxy/adapters/anthropic.ts index 1121641b641..5bf27cb8449 100644 --- a/platform/backend/src/routes/proxy/adapters/anthropic.ts +++ b/platform/backend/src/routes/proxy/adapters/anthropic.ts @@ -608,6 +608,10 @@ class AnthropicStreamAdapter { readonly provider = "anthropic" as const; readonly state: StreamAccumulatorState; + // SSE encoding of each state.rawToolCallEvents entry, cached at accumulation + // time. getRawToolCallEvents runs once per tool-call chunk, so serializing + // there would re-encode the whole history per chunk (O(k^2) per stream). + private serializedToolCallEvents: string[] = []; private toolUseBlockIndices = new Set(); private currentToolCallIndex = -1; // Highest content-block index actually forwarded to the client, so a refusal @@ -681,7 +685,7 @@ class AnthropicStreamAdapter arguments: "", }); // Store raw event for replay after policy approval - this.state.rawToolCallEvents.push(chunk); + this.accumulateRawToolCallEvent(chunk); isToolCallChunk = true; } else { // Everything except client tool calls (text, thinking, @@ -707,7 +711,7 @@ class AnthropicStreamAdapter chunk.delta.partial_json; } // Store raw event for replay after policy approval - this.state.rawToolCallEvents.push(chunk); + this.accumulateRawToolCallEvent(chunk); isToolCallChunk = true; } else { // input_json_delta outside a tool_use block belongs to a @@ -724,7 +728,7 @@ class AnthropicStreamAdapter sseData = `event: content_block_stop\ndata: ${JSON.stringify(chunk)}\n\n`; } else { // Store raw event for replay after policy approval - this.state.rawToolCallEvents.push(chunk); + this.accumulateRawToolCallEvent(chunk); isToolCallChunk = true; } break; @@ -782,10 +786,7 @@ class AnthropicStreamAdapter } getRawToolCallEvents(): string[] { - return this.state.rawToolCallEvents.map( - (event) => - `event: ${(event as { type: string }).type}\ndata: ${JSON.stringify(event)}\n\n`, - ); + return this.serializedToolCallEvents; } formatCompleteTextSSE(text: string): string[] { @@ -899,6 +900,13 @@ class AnthropicStreamAdapter }, }; } + + private accumulateRawToolCallEvent(chunk: AnthropicStreamChunk): void { + // Serialize before pushing so a throwing stringify can't desync the two arrays. + const serialized = `event: ${chunk.type}\ndata: ${JSON.stringify(chunk)}\n\n`; + this.state.rawToolCallEvents.push(chunk); + this.serializedToolCallEvents.push(serialized); + } } // ============================================================================= diff --git a/platform/backend/src/routes/proxy/adapters/bedrock.ts b/platform/backend/src/routes/proxy/adapters/bedrock.ts index 18091269a19..9db6ea17e2a 100644 --- a/platform/backend/src/routes/proxy/adapters/bedrock.ts +++ b/platform/backend/src/routes/proxy/adapters/bedrock.ts @@ -972,6 +972,12 @@ class BedrockStreamAdapter { readonly provider = "bedrock" as const; readonly state: StreamAccumulatorState; + // Event-stream encoding of each accumulated tool-call event (tool names + // already decoded) and of the buffered final events, cached at accumulation + // time. getRawToolCallEvents runs once per tool-call chunk, so encoding + // there would re-encode the whole history per chunk (O(k^2) per stream). + private encodedToolCallEvents: Uint8Array[] = []; + private encodedPendingFinalEvents: Uint8Array[] = []; private currentToolCallIndex = -1; private toolNameMapping: ToolNameMapping = createEmptyToolNameMapping(); // Set to the refusal text when the streamed response was replaced by a policy @@ -985,9 +991,6 @@ class BedrockStreamAdapter private bedrockState: { latencyMs: number | null; trace: unknown | null; - // Buffer for messageStop and metadata events when tool calls are pending - // These must be sent AFTER tool call events in the correct stream order - pendingFinalEvents: BedrockStreamEventWithRaw[]; }; constructor() { @@ -1007,7 +1010,6 @@ class BedrockStreamAdapter this.bedrockState = { latencyMs: null, trace: null, - pendingFinalEvents: [], }; } @@ -1045,13 +1047,28 @@ class BedrockStreamAdapter ) { // Tool use block - buffer for policy evaluation const toolUse = blockStart.start.toolUse; + const decodedName = decodeToolName( + toolUse.name ?? "", + this.toolNameMapping, + ); this.currentToolCallIndex = this.state.toolCalls.length; this.state.toolCalls.push({ id: toolUse.toolUseId ?? "", - name: decodeToolName(toolUse.name ?? "", this.toolNameMapping), + name: decodedName, arguments: "", }); + // Re-encode with the decoded tool name for replay; the raw bytes + // contain the encoded name (hyphens replaced with underscores). + // Encode before pushing so a throwing encoder can't desync the arrays. + const encodedBlockStart = encodeEventStreamMessage( + "contentBlockStart", + { + ...blockStart, + start: { toolUse: { ...toolUse, name: decodedName } }, + }, + ); this.state.rawToolCallEvents.push(chunk); + this.encodedToolCallEvents.push(encodedBlockStart); isToolCallChunk = true; } else { sseData = @@ -1086,7 +1103,12 @@ class BedrockStreamAdapter this.state.toolCalls[this.currentToolCallIndex].arguments += toolUseDelta.input; } + const encodedBlockDelta = encodeEventStreamMessage( + "contentBlockDelta", + chunk.contentBlockDelta, + ); this.state.rawToolCallEvents.push(chunk); + this.encodedToolCallEvents.push(encodedBlockDelta); isToolCallChunk = true; } } else if ("contentBlockStop" in chunk && chunk.contentBlockStop) { @@ -1095,7 +1117,12 @@ class BedrockStreamAdapter this.currentToolCallIndex === this.state.toolCalls.length - 1; if (isToolBlock) { + const encodedBlockStop = encodeEventStreamMessage( + "contentBlockStop", + chunk.contentBlockStop, + ); this.state.rawToolCallEvents.push(chunk); + this.encodedToolCallEvents.push(encodedBlockStop); isToolCallChunk = true; } else { sseData = @@ -1107,7 +1134,12 @@ class BedrockStreamAdapter // If we have pending tool calls, buffer this event to send after tool blocks // The stream order must be: text blocks → tool blocks → messageStop → metadata if (this.state.toolCalls.length > 0) { - this.bedrockState.pendingFinalEvents.push(chunk); + // Buffer for replay after the tool events; raw bytes are safe here + // because final events carry no tool names. + this.encodedPendingFinalEvents.push( + rawBytes ?? + encodeEventStreamMessage("messageStop", chunk.messageStop), + ); isToolCallChunk = true; // Mark as tool-related so it's not streamed yet } else { sseData = @@ -1144,7 +1176,10 @@ class BedrockStreamAdapter } // If we have pending tool calls, buffer this event to send after tool blocks if (this.state.toolCalls.length > 0) { - this.bedrockState.pendingFinalEvents.push(chunk); + // Raw bytes are safe here: final events carry no tool names. + this.encodedPendingFinalEvents.push( + rawBytes ?? encodeEventStreamMessage("metadata", chunk.metadata), + ); isToolCallChunk = true; // Mark as tool-related so it's not streamed yet } else { // Pass through metadata chunk as-is - this is the final event @@ -1237,80 +1272,11 @@ class BedrockStreamAdapter } getRawToolCallEvents(): Uint8Array[] { - const result: Uint8Array[] = []; - - // Re-encode all tool call content blocks with decoded tool names - // We cannot use raw bytes because they contain encoded names (hyphens replaced with underscores) - for (const rawEvent of this.state.rawToolCallEvents) { - const event = rawEvent as BedrockStreamEventWithRaw; - - if ("contentBlockStart" in event && event.contentBlockStart) { - const blockStart = event.contentBlockStart; - // Decode tool name if this is a tool use block - if ( - blockStart.start && - "toolUse" in blockStart.start && - blockStart.start.toolUse - ) { - const originalName = blockStart.start.toolUse.name ?? ""; - const decodedName = decodeToolName( - originalName, - this.toolNameMapping, - ); - const decodedEvent = { - ...blockStart, - start: { - toolUse: { - ...blockStart.start.toolUse, - name: decodedName, - }, - }, - }; - result.push( - encodeEventStreamMessage("contentBlockStart", decodedEvent), - ); - } else { - result.push( - encodeEventStreamMessage( - "contentBlockStart", - event.contentBlockStart, - ), - ); - } - } else if ("contentBlockDelta" in event && event.contentBlockDelta) { - result.push( - encodeEventStreamMessage( - "contentBlockDelta", - event.contentBlockDelta, - ), - ); - } else if ("contentBlockStop" in event && event.contentBlockStop) { - result.push( - encodeEventStreamMessage("contentBlockStop", event.contentBlockStop), - ); - } - } - - // Then, add the buffered final events (messageStop and metadata) in order - // These must come AFTER all content blocks for correct stream order - for (const finalEvent of this.bedrockState.pendingFinalEvents) { - const event = finalEvent as BedrockStreamEventWithRaw; - - // Use original raw bytes if available (these don't contain tool names) - if (event.__rawBytes) { - result.push(event.__rawBytes); - continue; - } - - // Fallback to re-encoding - if ("messageStop" in event && event.messageStop) { - result.push(encodeEventStreamMessage("messageStop", event.messageStop)); - } else if ("metadata" in event && event.metadata) { - result.push(encodeEventStreamMessage("metadata", event.metadata)); - } - } - - return result; + // Both caches are encoded at accumulation time. The buffered final events + // (messageStop and metadata) must come AFTER all content blocks for + // correct stream order; Bedrock emits them last, so appending the second + // cache preserves arrival order and keeps indices stable across calls. + return [...this.encodedToolCallEvents, ...this.encodedPendingFinalEvents]; } formatCompleteTextSSE(text: string): Uint8Array[] { diff --git a/platform/backend/src/routes/proxy/adapters/cohere-openai.ts b/platform/backend/src/routes/proxy/adapters/cohere-openai.ts index 1784e2585bb..e63661c59f4 100644 --- a/platform/backend/src/routes/proxy/adapters/cohere-openai.ts +++ b/platform/backend/src/routes/proxy/adapters/cohere-openai.ts @@ -139,6 +139,12 @@ class CohereOpenaiStreamAdapter } getRawToolCallEvents(): string[] { + // Known non-conforming (ledger: tool-events-untranslated-empty-history): + // this wrapper never translates Cohere tool-call events to OpenAI SSE, so + // model-router Cohere tool calls are never streamed or flushed to clients. + // Implementing that translation is new behavior tracked separately, not a + // replay-contract fix, so this adapter is exempt from the full-history + // contract documented on LLMStreamAdapter.getRawToolCallEvents. return []; } diff --git a/platform/backend/src/routes/proxy/adapters/openai.ts b/platform/backend/src/routes/proxy/adapters/openai.ts index 0ef2394cb93..ea8ed90f242 100644 --- a/platform/backend/src/routes/proxy/adapters/openai.ts +++ b/platform/backend/src/routes/proxy/adapters/openai.ts @@ -991,6 +991,10 @@ export class OpenAIStreamAdapter readonly provider: SupportedProvider; readonly state: StreamAccumulatorState; private currentToolCallIndices = new Map(); + // SSE encoding of each state.rawToolCallEvents entry, cached at accumulation + // time. getRawToolCallEvents runs once per tool-call chunk, so serializing + // there would re-encode the whole history per chunk (O(k^2) per stream). + private serializedToolCallEvents: string[] = []; // Set to the refusal text when the streamed response was replaced by a policy // refusal. formatEndSSE then finishes the turn as "stop" instead of replaying // the upstream "tool_calls" finish reason (a text-only turn ending in @@ -1104,7 +1108,10 @@ export class OpenAIStreamAdapter } } + // Serialize before pushing so a throwing stringify can't desync the two arrays. + const serializedChunk = `data: ${JSON.stringify(chunk)}\n\n`; this.state.rawToolCallEvents.push(chunk); + this.serializedToolCallEvents.push(serializedChunk); isToolCallChunk = true; } @@ -1152,9 +1159,7 @@ export class OpenAIStreamAdapter } getRawToolCallEvents(): string[] { - return this.state.rawToolCallEvents.map( - (event) => `data: ${JSON.stringify(event)}\n\n`, - ); + return this.serializedToolCallEvents; } formatCompleteTextSSE(text: string): string[] { diff --git a/platform/backend/src/routes/proxy/adapters/stream-replay.test.ts b/platform/backend/src/routes/proxy/adapters/stream-replay.test.ts new file mode 100644 index 00000000000..88c5622f9ba --- /dev/null +++ b/platform/backend/src/routes/proxy/adapters/stream-replay.test.ts @@ -0,0 +1,678 @@ +/** + * Exact full-event-sequence tests for the streaming tool-call replay path. + * + * The proxy handler replays tool-call events through + * `getRawToolCallEvents()` and dedups by ARRAY INDEX + * (llm-proxy-handler.ts:1213-1222, :1388-1401), so every stream adapter must + * return the full, index-stable, append-only event history on every call. + * + * Each test drives an adapter with a synthetic chunk stream through the exact + * write pattern of the handler and asserts the COMPLETE ordered sequence of + * SSE writes, byte for byte, for three scenarios: + * - immediately-streamed non-blocking tool call (replay after every chunk), + * - buffered-then-approved final flush, + * - blocked-then-refused discard. + * + * The three native paths (OpenAI, Anthropic, Bedrock) pin byte-identical + * pre-existing behavior. The model-router Anthropic->OpenAI wrapper pins the + * FIXED behavior: the tool name event AND every argument-delta event are + * delivered exactly once (previously the wrapper drained its buffer on each + * getter call, re-basing indices at 0, so the handler's index dedup dropped + * every event after the first). + */ +import { EventStreamCodec } from "@smithy/eventstream-codec"; +import { fromUtf8, toUtf8 } from "@smithy/util-utf8"; +import { vi } from "vitest"; +import { afterEach, beforeEach, describe, expect, test } from "@/test"; +import type { LLMStreamAdapter, OpenAi } from "@/types"; +import { anthropicAdapterFactory } from "./anthropic"; +import { makeAnthropicOpenaiAdapterFactory } from "./anthropic-openai"; +import { bedrockAdapterFactory } from "./bedrock"; +import { openaiAdapterFactory } from "./openai"; + +const FAKE_NOW_MS = 1_767_225_600_000; // 2026-01-01T00:00:00Z +const FAKE_NOW_UNIX = Math.floor(FAKE_NOW_MS / 1000); +const REFUSAL_TEXT = "This tool call was blocked by policy."; + +const ARGUMENT_FRAGMENTS = ['{"query":"', "weather ", 'today"', "}"] as const; + +type Scenario = + | "streamed-non-blocking" + | "buffered-approved" + | "blocked-refused"; + +type SseWrite = string | Uint8Array; + +/** + * Replicates the handler's SSE write pattern exactly: + * - per-chunk: llm-proxy-handler.ts:1174-1230 (text sseData streams + * immediately; in the non-blocking scenario every tool-call chunk triggers + * a full replay deduped by array index), + * - post-stream: llm-proxy-handler.ts:1361-1405 (refusal events, or the + * final flush of un-streamed tool events, then formatEndSSE). + */ +function runHandlerWritePattern(params: { + adapter: LLMStreamAdapter; + chunks: TChunk[]; + scenario: Scenario; +}): SseWrite[] { + const { adapter, chunks, scenario } = params; + const writes: SseWrite[] = []; + const streamedEventIndices = new Set(); + + for (const chunk of chunks) { + const result = adapter.processChunk(chunk); + if (result.sseData) { + writes.push(result.sseData); + } else if (result.isToolCallChunk && scenario === "streamed-non-blocking") { + const allEvents = adapter.getRawToolCallEvents(); + for (let i = 0; i < allEvents.length; i++) { + if (!streamedEventIndices.has(i)) { + writes.push(allEvents[i]); + streamedEventIndices.add(i); + } + } + } + if (result.isFinal) { + break; + } + } + + if (scenario === "blocked-refused") { + for (const event of adapter.formatCompleteTextSSE(REFUSAL_TEXT)) { + writes.push(event); + } + } else if ( + adapter.state.toolCalls.length > 0 && + streamedEventIndices.size < adapter.getRawToolCallEvents().length + ) { + const allEvents = adapter.getRawToolCallEvents(); + for (let i = 0; i < allEvents.length; i++) { + if (!streamedEventIndices.has(i)) { + writes.push(allEvents[i]); + } + } + } + + writes.push(adapter.formatEndSSE()); + return writes; +} + +beforeEach(() => { + vi.useFakeTimers({ now: FAKE_NOW_MS }); +}); + +afterEach(() => { + vi.useRealTimers(); +}); + +// ============================================================================= +// Native OpenAI +// ============================================================================= + +const OPENAI_ID = "chatcmpl-replay-test"; +const OPENAI_MODEL = "gpt-test"; + +function openaiChunks(): OpenAi.Types.ChatCompletionChunk[] { + const base = { + id: OPENAI_ID, + object: "chat.completion.chunk" as const, + created: 1_700_000_000, + model: OPENAI_MODEL, + }; + return [ + { + ...base, + choices: [ + { + index: 0, + delta: { content: "Checking " }, + finish_reason: null, + logprobs: null, + }, + ], + }, + { + ...base, + choices: [ + { + index: 0, + delta: { + tool_calls: [ + { + index: 0, + id: "call_replay_0", + type: "function", + function: { name: "search_documents", arguments: "" }, + }, + ], + }, + finish_reason: null, + logprobs: null, + }, + ], + }, + ...ARGUMENT_FRAGMENTS.map((fragment) => ({ + ...base, + choices: [ + { + index: 0, + delta: { + tool_calls: [{ index: 0, function: { arguments: fragment } }], + }, + finish_reason: null, + logprobs: null, + }, + ], + })), + { + ...base, + choices: [ + { index: 0, delta: {}, finish_reason: "tool_calls", logprobs: null }, + ], + }, + { + ...base, + choices: [], + usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 }, + }, + ] as OpenAi.Types.ChatCompletionChunk[]; +} + +// Expected serializations are computed from fresh chunk literals BEFORE the +// adapter sees anything, so a post-accumulation mutation inside the adapter +// would fail these tests. +const openaiExpected = { + textEvent: () => `data: ${JSON.stringify(openaiChunks()[0])}\n\n`, + toolEvents: () => + openaiChunks() + .slice(1, 6) + .map((chunk) => `data: ${JSON.stringify(chunk)}\n\n`), + endEvent: (finishReason: "tool_calls" | "stop") => + `data: ${JSON.stringify({ + id: OPENAI_ID, + object: "chat.completion.chunk", + created: FAKE_NOW_UNIX, + model: OPENAI_MODEL, + choices: [{ index: 0, delta: {}, finish_reason: finishReason }], + usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 }, + })}\n\ndata: [DONE]\n\n`, + refusalEvent: () => + `data: ${JSON.stringify({ + id: OPENAI_ID, + object: "chat.completion.chunk", + created: FAKE_NOW_UNIX, + model: OPENAI_MODEL, + choices: [ + { + index: 0, + delta: { role: "assistant", content: REFUSAL_TEXT }, + finish_reason: null, + }, + ], + })}\n\n`, +}; + +describe("native OpenAI stream replay", () => { + test("streamed non-blocking tool call delivers every event once, in order", () => { + const writes = runHandlerWritePattern({ + adapter: openaiAdapterFactory.createStreamAdapter(), + chunks: openaiChunks(), + scenario: "streamed-non-blocking", + }); + + expect(writes).toEqual([ + openaiExpected.textEvent(), + ...openaiExpected.toolEvents(), + openaiExpected.endEvent("tool_calls"), + ]); + }); + + test("buffered-then-approved flushes the full event history at the end", () => { + const writes = runHandlerWritePattern({ + adapter: openaiAdapterFactory.createStreamAdapter(), + chunks: openaiChunks(), + scenario: "buffered-approved", + }); + + expect(writes).toEqual([ + openaiExpected.textEvent(), + ...openaiExpected.toolEvents(), + openaiExpected.endEvent("tool_calls"), + ]); + }); + + test("blocked-then-refused discards tool events and sends the refusal", () => { + const writes = runHandlerWritePattern({ + adapter: openaiAdapterFactory.createStreamAdapter(), + chunks: openaiChunks(), + scenario: "blocked-refused", + }); + + expect(writes).toEqual([ + openaiExpected.textEvent(), + openaiExpected.refusalEvent(), + openaiExpected.endEvent("stop"), + ]); + }); + + test("getRawToolCallEvents is non-destructive and index-stable across calls", () => { + const adapter = openaiAdapterFactory.createStreamAdapter(); + for (const chunk of openaiChunks()) { + adapter.processChunk(chunk); + } + const first = adapter.getRawToolCallEvents(); + const second = adapter.getRawToolCallEvents(); + expect([...first]).toEqual(openaiExpected.toolEvents()); + expect([...second]).toEqual([...first]); + }); +}); + +// ============================================================================= +// Native Anthropic +// ============================================================================= + +const ANTHROPIC_MESSAGE_ID = "msg-replay-test"; +const ANTHROPIC_MODEL = "claude-test"; + +type AnthropicStreamChunk = Parameters< + ReturnType["processChunk"] +>[0]; + +function anthropicChunkPayloads(): Record[] { + return [ + { + type: "message_start", + message: { + id: ANTHROPIC_MESSAGE_ID, + type: "message", + role: "assistant", + content: [], + model: ANTHROPIC_MODEL, + stop_reason: null, + stop_sequence: null, + usage: { input_tokens: 25, output_tokens: 1 }, + }, + }, + { + type: "content_block_start", + index: 0, + content_block: { + type: "tool_use", + id: "toolu_replay_0", + name: "search_documents", + input: {}, + }, + }, + ...ARGUMENT_FRAGMENTS.map((partial_json) => ({ + type: "content_block_delta", + index: 0, + delta: { type: "input_json_delta", partial_json }, + })), + { type: "content_block_stop", index: 0 }, + { + type: "message_delta", + delta: { stop_reason: "tool_use", stop_sequence: null }, + usage: { output_tokens: 9 }, + }, + { type: "message_stop" }, + ]; +} + +function anthropicChunks(): AnthropicStreamChunk[] { + return anthropicChunkPayloads() as unknown as AnthropicStreamChunk[]; +} + +function anthropicSse(payload: Record): string { + return `event: ${payload.type}\ndata: ${JSON.stringify(payload)}\n\n`; +} + +const anthropicExpected = { + messageStartEvent: () => anthropicSse(anthropicChunkPayloads()[0]), + toolEvents: () => anthropicChunkPayloads().slice(1, 7).map(anthropicSse), + endEvent: (stopReason: "tool_use" | "end_turn") => + anthropicSse({ + type: "message_delta", + delta: { stop_reason: stopReason, stop_sequence: null }, + usage: { output_tokens: 9 }, + }) + anthropicSse({ type: "message_stop" }), + refusalEvents: () => [ + anthropicSse({ + type: "content_block_start", + index: 0, + content_block: { type: "text", text: "" }, + }), + anthropicSse({ + type: "content_block_delta", + index: 0, + delta: { type: "text_delta", text: REFUSAL_TEXT }, + }), + anthropicSse({ type: "content_block_stop", index: 0 }), + ], +}; + +describe("native Anthropic stream replay", () => { + test("streamed non-blocking tool call delivers every event once, in order", () => { + const writes = runHandlerWritePattern({ + adapter: anthropicAdapterFactory.createStreamAdapter(), + chunks: anthropicChunks(), + scenario: "streamed-non-blocking", + }); + + expect(writes).toEqual([ + anthropicExpected.messageStartEvent(), + ...anthropicExpected.toolEvents(), + anthropicExpected.endEvent("tool_use"), + ]); + }); + + test("buffered-then-approved flushes the full event history at the end", () => { + const writes = runHandlerWritePattern({ + adapter: anthropicAdapterFactory.createStreamAdapter(), + chunks: anthropicChunks(), + scenario: "buffered-approved", + }); + + expect(writes).toEqual([ + anthropicExpected.messageStartEvent(), + ...anthropicExpected.toolEvents(), + anthropicExpected.endEvent("tool_use"), + ]); + }); + + test("blocked-then-refused discards tool events and sends the refusal", () => { + const writes = runHandlerWritePattern({ + adapter: anthropicAdapterFactory.createStreamAdapter(), + chunks: anthropicChunks(), + scenario: "blocked-refused", + }); + + expect(writes).toEqual([ + anthropicExpected.messageStartEvent(), + ...anthropicExpected.refusalEvents(), + anthropicExpected.endEvent("end_turn"), + ]); + }); + + test("getRawToolCallEvents is non-destructive and index-stable across calls", () => { + const adapter = anthropicAdapterFactory.createStreamAdapter(); + for (const chunk of anthropicChunks()) { + adapter.processChunk(chunk); + } + const first = adapter.getRawToolCallEvents(); + const second = adapter.getRawToolCallEvents(); + expect([...first]).toEqual(anthropicExpected.toolEvents()); + expect([...second]).toEqual([...first]); + }); +}); + +// ============================================================================= +// Native Bedrock +// ============================================================================= + +type BedrockStreamChunk = Parameters< + ReturnType["processChunk"] +>[0]; + +const bedrockCodec = new EventStreamCodec(toUtf8, fromUtf8); + +// Independent re-implementation of the adapter's AWS event-stream encoding +// (bedrock.ts encodeEventStreamMessage + generatePadding) so expected bytes +// are pinned by the test, not derived from the code under test. +function encodeBedrockEvent( + eventType: string, + body: Record, +): Uint8Array { + const bodyWithoutPadding = JSON.stringify(body); + const paddingAlphabet = + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + const paddingNeeded = Math.max(0, 80 - bodyWithoutPadding.length - 10); + const padding = paddingAlphabet.slice( + 0, + Math.min(paddingNeeded, paddingAlphabet.length), + ); + return bedrockCodec.encode({ + headers: { + ":event-type": { type: "string", value: eventType }, + ":content-type": { type: "string", value: "application/json" }, + ":message-type": { type: "string", value: "event" }, + }, + body: fromUtf8(JSON.stringify({ ...body, p: padding })), + }); +} + +function concatBytes(a: Uint8Array, b: Uint8Array): Uint8Array { + const out = new Uint8Array(a.length + b.length); + out.set(a, 0); + out.set(b, a.length); + return out; +} + +function bedrockChunks(): BedrockStreamChunk[] { + return [ + { messageStart: { role: "assistant" } }, + { + contentBlockStart: { + contentBlockIndex: 0, + start: { + toolUse: { toolUseId: "tooluse_replay_0", name: "search_documents" }, + }, + }, + }, + ...ARGUMENT_FRAGMENTS.map((input) => ({ + contentBlockDelta: { + contentBlockIndex: 0, + delta: { toolUse: { input } }, + }, + })), + { contentBlockStop: { contentBlockIndex: 0 } }, + { messageStop: { stopReason: "tool_use" } }, + { metadata: { usage: { inputTokens: 25, outputTokens: 9 } } }, + ] as BedrockStreamChunk[]; +} + +const bedrockExpected = { + messageStartEvent: () => + encodeBedrockEvent("messageStart", { role: "assistant" }), + toolEvents: () => [ + encodeBedrockEvent("contentBlockStart", { + contentBlockIndex: 0, + start: { + toolUse: { toolUseId: "tooluse_replay_0", name: "search_documents" }, + }, + }), + ...ARGUMENT_FRAGMENTS.map((input) => + encodeBedrockEvent("contentBlockDelta", { + contentBlockIndex: 0, + delta: { toolUse: { input } }, + }), + ), + encodeBedrockEvent("contentBlockStop", { contentBlockIndex: 0 }), + encodeBedrockEvent("messageStop", { stopReason: "tool_use" }), + encodeBedrockEvent("metadata", { + usage: { inputTokens: 25, outputTokens: 9 }, + }), + ], + refusalEvents: () => [ + encodeBedrockEvent("contentBlockStart", { + contentBlockIndex: 0, + start: { text: "" }, + }), + encodeBedrockEvent("contentBlockDelta", { + contentBlockIndex: 0, + delta: { text: REFUSAL_TEXT }, + }), + encodeBedrockEvent("contentBlockStop", { contentBlockIndex: 0 }), + ], + refusalEndEvent: () => + concatBytes( + encodeBedrockEvent("messageStop", { stopReason: "end_turn" }), + encodeBedrockEvent("metadata", { + usage: { inputTokens: 25, outputTokens: 9 }, + }), + ), +}; + +describe("native Bedrock stream replay", () => { + test("streamed non-blocking tool call delivers every event once, in order", () => { + const writes = runHandlerWritePattern({ + adapter: bedrockAdapterFactory.createStreamAdapter(), + chunks: bedrockChunks(), + scenario: "streamed-non-blocking", + }); + + expect(writes).toEqual([ + bedrockExpected.messageStartEvent(), + ...bedrockExpected.toolEvents(), + // Bedrock's formatEndSSE is empty on the non-refusal path: messageStop + // and metadata were already replayed with the tool events. + "", + ]); + }); + + test("buffered-then-approved flushes the full event history at the end", () => { + const writes = runHandlerWritePattern({ + adapter: bedrockAdapterFactory.createStreamAdapter(), + chunks: bedrockChunks(), + scenario: "buffered-approved", + }); + + expect(writes).toEqual([ + bedrockExpected.messageStartEvent(), + ...bedrockExpected.toolEvents(), + "", + ]); + }); + + test("blocked-then-refused discards tool events and sends the refusal", () => { + const writes = runHandlerWritePattern({ + adapter: bedrockAdapterFactory.createStreamAdapter(), + chunks: bedrockChunks(), + scenario: "blocked-refused", + }); + + expect(writes).toEqual([ + bedrockExpected.messageStartEvent(), + ...bedrockExpected.refusalEvents(), + bedrockExpected.refusalEndEvent(), + ]); + }); + + test("getRawToolCallEvents is non-destructive and index-stable across calls", () => { + const adapter = bedrockAdapterFactory.createStreamAdapter(); + for (const chunk of bedrockChunks()) { + adapter.processChunk(chunk); + } + const first = adapter.getRawToolCallEvents(); + const second = adapter.getRawToolCallEvents(); + expect([...first]).toEqual(bedrockExpected.toolEvents()); + expect([...second]).toEqual([...first]); + }); +}); + +// ============================================================================= +// Model-router Anthropic -> OpenAI wrapper +// ============================================================================= + +const WRAPPER_CTX = { + chatcmplId: "chatcmpl-router-test", + createdUnix: 1_700_000_123, + requestedModel: "anthropic:claude-test", +}; + +function wrapperAdapter() { + return makeAnthropicOpenaiAdapterFactory(WRAPPER_CTX).createStreamAdapter(); +} + +function wrapperChunkSse( + delta: Record, + finishReason: string | null = null, +): string { + return `data: ${JSON.stringify({ + id: WRAPPER_CTX.chatcmplId, + object: "chat.completion.chunk", + created: WRAPPER_CTX.createdUnix, + model: WRAPPER_CTX.requestedModel, + choices: [{ index: 0, delta, finish_reason: finishReason, logprobs: null }], + })}\n\n`; +} + +const wrapperExpected = { + roleEvent: () => wrapperChunkSse({ role: "assistant" }), + toolEvents: () => [ + wrapperChunkSse({ + tool_calls: [ + { + index: 0, + id: "toolu_replay_0", + type: "function", + function: { name: "search_documents", arguments: "" }, + }, + ], + }), + ...ARGUMENT_FRAGMENTS.map((fragment) => + wrapperChunkSse({ + tool_calls: [{ index: 0, function: { arguments: fragment } }], + }), + ), + ], + endEvent: (finishReason: "tool_calls" | "stop") => + `${wrapperChunkSse({}, finishReason)}data: [DONE]\n\n`, + refusalEvent: () => + wrapperChunkSse({ role: "assistant", content: REFUSAL_TEXT }), +}; + +describe("model-router Anthropic->OpenAI wrapper stream replay (fixed behavior)", () => { + test("streamed non-blocking tool call delivers the name event and every argument delta once", () => { + const writes = runHandlerWritePattern({ + adapter: wrapperAdapter(), + chunks: anthropicChunks(), + scenario: "streamed-non-blocking", + }); + + expect(writes).toEqual([ + wrapperExpected.roleEvent(), + ...wrapperExpected.toolEvents(), + wrapperExpected.endEvent("tool_calls"), + ]); + }); + + test("buffered-then-approved flushes the full event history at the end", () => { + const writes = runHandlerWritePattern({ + adapter: wrapperAdapter(), + chunks: anthropicChunks(), + scenario: "buffered-approved", + }); + + expect(writes).toEqual([ + wrapperExpected.roleEvent(), + ...wrapperExpected.toolEvents(), + wrapperExpected.endEvent("tool_calls"), + ]); + }); + + test("blocked-then-refused discards tool events and sends the refusal", () => { + const writes = runHandlerWritePattern({ + adapter: wrapperAdapter(), + chunks: anthropicChunks(), + scenario: "blocked-refused", + }); + + expect(writes).toEqual([ + wrapperExpected.roleEvent(), + wrapperExpected.refusalEvent(), + wrapperExpected.endEvent("stop"), + ]); + }); + + test("getRawToolCallEvents is non-destructive and index-stable across calls", () => { + const adapter = wrapperAdapter(); + for (const chunk of anthropicChunks()) { + adapter.processChunk(chunk); + } + const first = adapter.getRawToolCallEvents(); + const second = adapter.getRawToolCallEvents(); + expect([...first]).toEqual(wrapperExpected.toolEvents()); + expect([...second]).toEqual([...first]); + }); +}); diff --git a/platform/backend/src/routes/proxy/routes/model-router.test.ts b/platform/backend/src/routes/proxy/routes/model-router.test.ts index d930a0fbc3b..f6b97dbfb1e 100644 --- a/platform/backend/src/routes/proxy/routes/model-router.test.ts +++ b/platform/backend/src/routes/proxy/routes/model-router.test.ts @@ -1947,6 +1947,125 @@ describe("model router proxy routes", () => { expect(response.body).not.toContain("event: message_start"); }); + test("streams Anthropic model router tool calls with the name event and every argument delta", async ({ + makeAgent, + makeOrganization, + makeSecret, + makeLlmProviderApiKey, + }) => { + anthropicStubOptions.includeToolUse = true; + const app = createFastifyApp(); + await app.register(modelRouterProxyRoutes); + await upsertModel({ + provider: "anthropic", + modelId: "claude-opus-4-6-20250918", + }); + const organization = await makeOrganization(); + const { value } = await createModelRouterVirtualKey({ + organizationId: organization.id, + provider: "anthropic", + makeSecret, + makeLlmProviderApiKey, + apiKeyValue: "test-anthropic-key", + }); + const agent = await makeAgent({ + organizationId: organization.id, + name: "Model Router Tool Streaming Agent", + agentType: "llm_proxy", + }); + + const response = await app.inject({ + method: "POST", + url: `/v1/model-router/${agent.id}/chat/completions`, + headers: { + "content-type": "application/json", + authorization: `Bearer ${value}`, + "user-agent": "test-client", + }, + payload: { + model: "anthropic:claude-opus-4-6-20250918", + messages: [{ role: "user", content: "What is the weather?" }], + stream: true, + }, + }); + + expect(response.statusCode).toBe(200); + + const dataLines = response.body + .split("\n\n") + .map((event) => event.trim()) + .filter((event) => event.startsWith("data: ")) + .map((event) => event.slice("data: ".length)); + expect(dataLines.at(-1)).toBe("[DONE]"); + + // The complete ordered choice sequence: the tool-call name event and every + // argument delta must each arrive exactly once. The wrapper adapter used + // to drain its event buffer per getRawToolCallEvents call, which made the + // handler's index dedup drop every argument delta after the name event. + const choices = dataLines.slice(0, -1).map((line) => { + const chunk = JSON.parse(line) as { + object: string; + choices: Array<{ delta: unknown; finish_reason: string | null }>; + }; + expect(chunk.object).toBe("chat.completion.chunk"); + return chunk.choices[0]; + }); + expect(choices).toEqual([ + { + index: 0, + delta: { role: "assistant" }, + finish_reason: null, + logprobs: null, + }, + { + index: 0, + delta: { + tool_calls: [ + { + index: 0, + id: "toolu_test_weather", + type: "function", + function: { name: "get_weather", arguments: "" }, + }, + ], + }, + finish_reason: null, + logprobs: null, + }, + { + index: 0, + delta: { + tool_calls: [{ index: 0, function: { arguments: '{"location":"' } }], + }, + finish_reason: null, + logprobs: null, + }, + { + index: 0, + delta: { + tool_calls: [ + { index: 0, function: { arguments: 'San Francisco",' } }, + ], + }, + finish_reason: null, + logprobs: null, + }, + { + index: 0, + delta: { + tool_calls: [ + { index: 0, function: { arguments: '"unit":"fahrenheit"}' } }, + ], + }, + finish_reason: null, + logprobs: null, + }, + // The stub's message_delta reports stop_reason "end_turn", which the + // wrapper maps to "stop". + { index: 0, delta: {}, finish_reason: "stop", logprobs: null }, + ]); + }); + test("lists only models for providers mapped on the virtual key", async ({ makeAgent, makeOrganization, diff --git a/platform/backend/src/types/llm-provider.ts b/platform/backend/src/types/llm-provider.ts index 80192093189..35b895855af 100644 --- a/platform/backend/src/types/llm-provider.ts +++ b/platform/backend/src/types/llm-provider.ts @@ -305,7 +305,23 @@ export interface LLMStreamAdapter { */ formatTextDeltaSSE(text: string): string | Uint8Array; - /** Get raw tool call events as SSE strings (for replay after policy approval) */ + /** + * Get raw tool call events as SSE-encoded payloads, for replay after tool + * invocation policy evaluation. + * + * Contract (the streaming proxy handler depends on every point — it calls + * this after every tool-call chunk and again at final flush, deduplicating + * replayed events by ARRAY INDEX): + * - Returns the FULL event history accumulated so far, in arrival order. + * - Append-only: an event's index never changes across calls, and events + * are never dropped or re-based. + * - Non-destructive: calling this any number of times must not change what + * subsequent calls return. + * + * Because the getter runs once per tool-call chunk, implementations should + * serialize each event once at accumulation time and return the cached + * encodings; re-serializing the whole history per call is O(k^2) per stream. + */ getRawToolCallEvents(): (string | Uint8Array)[]; /** From 00488104178830c20142a06c64bd512470f6ff1d Mon Sep 17 00:00:00 2001 From: Arseny Kravchenko Date: Fri, 10 Jul 2026 11:00:33 +0200 Subject: [PATCH 03/18] feat(archestra-rs): proxy-transform crate pair with TOON kernel proxy-transform-core: batched unwrap -> parse -> TOON-encode kernel (toon-format 0.5.0, default-features off; serde_json preserve_order). Positional infallible per-item API; parse failure -> encoded: null. Ports the unwrap-tool-content semantics exactly (first-text-element behavior pinned); 120-case golden corpus generated from the crate (regen via UPDATE_TOON_GOLDENS=1, CI-guarded) plus proptest decode round-trips with documented upstream-decoder-bug exclusions. proxy-transform-rs: thin NAPI adapter on the image-rs AsyncTask pattern (libuv pool, JS-thread input conversion, catch_unwind firewall, {code,message} error JSON), napi-loader index.cjs, CJS+ESM smoke tests; check:ci includes clippy -D warnings for both crates. Claude-Session: https://claude.ai/code/session_01QkeLJprfeq75d2NY39aFiu --- platform/.gitignore | 1 + platform/archestra-rs/Cargo.lock | 114 +- platform/archestra-rs/Cargo.toml | 11 +- .../proxy-transform-core/Cargo.toml | 20 + .../proxy-transform-core/src/lib.rs | 284 +++++ .../tests/fixtures/gen-corpus.mts | 191 +++ .../tests/fixtures/golden-corpus.json | 1082 +++++++++++++++++ .../tests/golden_corpus.rs | 101 ++ .../tests/roundtrip_property.rs | 206 ++++ .../proxy-transform-rs/Cargo.toml | 18 + .../archestra-rs/proxy-transform-rs/build.rs | 3 + .../archestra-rs/proxy-transform-rs/index.cjs | 16 + .../proxy-transform-rs/index.d.ts | 34 + .../proxy-transform-rs/package.json | 30 + .../proxy-transform-rs/smoke.esm.test.mjs | 18 + .../proxy-transform-rs/smoke.test.cjs | 39 + .../proxy-transform-rs/src/lib.rs | 71 ++ platform/pnpm-lock.yaml | 10 + platform/pnpm-workspace.yaml | 1 + 19 files changed, 2248 insertions(+), 2 deletions(-) create mode 100644 platform/archestra-rs/proxy-transform-core/Cargo.toml create mode 100644 platform/archestra-rs/proxy-transform-core/src/lib.rs create mode 100644 platform/archestra-rs/proxy-transform-core/tests/fixtures/gen-corpus.mts create mode 100644 platform/archestra-rs/proxy-transform-core/tests/fixtures/golden-corpus.json create mode 100644 platform/archestra-rs/proxy-transform-core/tests/golden_corpus.rs create mode 100644 platform/archestra-rs/proxy-transform-core/tests/roundtrip_property.rs create mode 100644 platform/archestra-rs/proxy-transform-rs/Cargo.toml create mode 100644 platform/archestra-rs/proxy-transform-rs/build.rs create mode 100644 platform/archestra-rs/proxy-transform-rs/index.cjs create mode 100644 platform/archestra-rs/proxy-transform-rs/index.d.ts create mode 100644 platform/archestra-rs/proxy-transform-rs/package.json create mode 100644 platform/archestra-rs/proxy-transform-rs/smoke.esm.test.mjs create mode 100644 platform/archestra-rs/proxy-transform-rs/smoke.test.cjs create mode 100644 platform/archestra-rs/proxy-transform-rs/src/lib.rs diff --git a/platform/.gitignore b/platform/.gitignore index f077dd40662..3f9413a552d 100644 --- a/platform/.gitignore +++ b/platform/.gitignore @@ -75,6 +75,7 @@ benchmark-config.env archestra-rs/sandbox-rs/index.js archestra-rs/app-runtime-rs/index.js archestra-rs/image-rs/index.js +archestra-rs/proxy-transform-rs/index.js # Playwright /playwright/ diff --git a/platform/archestra-rs/Cargo.lock b/platform/archestra-rs/Cargo.lock index db7c7a10e29..adb76d96c33 100644 --- a/platform/archestra-rs/Cargo.lock +++ b/platform/archestra-rs/Cargo.lock @@ -101,6 +101,21 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + [[package]] name = "bitflags" version = "1.3.2" @@ -1062,7 +1077,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3" dependencies = [ "byteorder-lite", - "quick-error", + "quick-error 2.0.1", ] [[package]] @@ -1582,6 +1597,25 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proptest" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags 2.13.0", + "num-traits", + "rand", + "rand_chacha", + "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", + "unarray", +] + [[package]] name = "prost" version = "0.14.4" @@ -1605,12 +1639,41 @@ dependencies = [ "syn 2.0.118", ] +[[package]] +name = "proxy_transform_core" +version = "0.1.0" +dependencies = [ + "napi", + "napi-derive", + "proptest", + "serde", + "serde_json", + "toon-format", +] + +[[package]] +name = "proxy_transform_rs" +version = "0.1.0" +dependencies = [ + "napi", + "napi-build", + "napi-derive", + "proxy_transform_core", + "serde_json", +] + [[package]] name = "pxfm" version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0c5ccf5294c6ccd63a74f1565028353830a9c2f5eb0c682c355c471726a6e3f" +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quick-error" version = "2.0.1" @@ -1723,6 +1786,15 @@ dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "rand_xorshift" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" +dependencies = [ + "rand_core", +] + [[package]] name = "redox_syscall" version = "0.5.18" @@ -1998,6 +2070,18 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +[[package]] +name = "rusty-fork" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2" +dependencies = [ + "fnv", + "quick-error 1.2.3", + "tempfile", + "wait-timeout", +] + [[package]] name = "ryu" version = "1.0.23" @@ -2150,6 +2234,7 @@ version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ + "indexmap", "itoa", "memchr", "serde", @@ -2506,6 +2591,18 @@ dependencies = [ "tokio", ] +[[package]] +name = "toon-format" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f89570c1a68d73941f728cca32a4345b2ffca36667ad921af336c60309a3e7e" +dependencies = [ + "indexmap", + "serde", + "serde_json", + "thiserror 2.0.18", +] + [[package]] name = "tower" version = "0.5.3" @@ -2641,6 +2738,12 @@ version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + [[package]] name = "unicode-ident" version = "1.0.24" @@ -2689,6 +2792,15 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + [[package]] name = "walkdir" version = "2.5.0" diff --git a/platform/archestra-rs/Cargo.toml b/platform/archestra-rs/Cargo.toml index b9afcdbf19e..5118bc12aad 100644 --- a/platform/archestra-rs/Cargo.toml +++ b/platform/archestra-rs/Cargo.toml @@ -1,5 +1,14 @@ [workspace] -members = ["app-runtime-core", "app-runtime-rs", "image-core", "image-rs", "sandbox-core", "sandbox-rs"] +members = [ + "app-runtime-core", + "app-runtime-rs", + "image-core", + "image-rs", + "proxy-transform-core", + "proxy-transform-rs", + "sandbox-core", + "sandbox-rs", +] resolver = "2" # No LTO: for these NAPI shims it measured slower to build AND a larger binary diff --git a/platform/archestra-rs/proxy-transform-core/Cargo.toml b/platform/archestra-rs/proxy-transform-core/Cargo.toml new file mode 100644 index 00000000000..a46bb9fb609 --- /dev/null +++ b/platform/archestra-rs/proxy-transform-core/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "proxy_transform_core" +version = "0.1.0" +edition = "2024" +publish = false + +[features] +default = [] +napi = ["dep:napi", "dep:napi-derive"] + +[dependencies] +napi = { version = "3", optional = true } +napi-derive = { version = "3", optional = true } +serde_json = { version = "1", features = ["preserve_order"] } +toon-format = { version = "0.5.0", default-features = false } + +[dev-dependencies] +proptest = "1" +# fixture (de)serialization in the golden-corpus test +serde = { version = "1", features = ["derive"] } diff --git a/platform/archestra-rs/proxy-transform-core/src/lib.rs b/platform/archestra-rs/proxy-transform-core/src/lib.rs new file mode 100644 index 00000000000..ab62b732e04 --- /dev/null +++ b/platform/archestra-rs/proxy-transform-core/src/lib.rs @@ -0,0 +1,284 @@ +//! Pure tool-result transformation kernel for the LLM proxy: unwrap the text-block +//! wrapper some clients (n8n, Vercel AI SDK) add around tool results, parse the +//! JSON, and encode it as TOON (spec v3, official `toon-format` crate). +//! +//! Node-free; the NAPI adapter lives in `proxy_transform_rs`. Per-item processing +//! is infallible: content that is not parseable JSON yields `encoded: None` and the +//! adapter keeps the original payload (fail-open, exactly like the TS path today). + +use serde_json::Value; + +/// One tool result to transform. `id` is the provider tool id, carried for +/// logging only — it is not unique across items (Anthropic reuses one +/// `tool_use_id` across blocks), so results are matched to inputs by position. +#[derive(Clone, Debug)] +#[cfg_attr(feature = "napi", napi_derive::napi(object))] +pub struct ToonEncodeItem { + pub id: String, + #[cfg_attr(feature = "napi", napi(js_name = "rawContent"))] + pub raw_content: String, + pub unwrap: bool, +} + +/// The transformation output for one item. `normalized` is the unwrapped string +/// when unwrapping was requested and matched, else the original `raw_content` +/// (adapters tokenize it for accounting). `encoded` is the TOON encoding, or +/// `None` when the content is not parseable JSON. +/// +/// `use_nullable` makes `encoded: None` cross the boundary as an explicit JS +/// `null` (typed `string | null`) instead of an omitted key. +#[derive(Clone, Debug)] +#[cfg_attr(feature = "napi", napi_derive::napi(object, use_nullable = true))] +pub struct ToonEncodeResult { + pub normalized: String, + pub encoded: Option, +} + +/// Transform a batch of tool results. Positional contract: the output has the +/// same length and order as the input. Never panics on any input. +pub fn toon_encode_tool_results(items: Vec) -> Vec { + items.into_iter().map(encode_item).collect() +} + +fn encode_item(item: ToonEncodeItem) -> ToonEncodeResult { + let normalized = if item.unwrap { + unwrap_tool_content(item.raw_content) + } else { + item.raw_content + }; + let encoded = serde_json::from_str::(&normalized) + .ok() + .and_then(|value| toon_format::encode_default(&value).ok()); + ToonEncodeResult { + normalized, + encoded, + } +} + +/// Port of `platform/backend/src/routes/proxy/utils/unwrap-tool-content.ts`: +/// if `content` parses as a JSON array whose FIRST element is +/// `{"type": "text", "text": , ...}`, return that text; otherwise return +/// `content` unchanged. First-element-only is deliberate (pinned TS behavior) — +/// extra wrapper elements are dropped from the encoding input. +/// +/// Divergence from JS `JSON.parse` (within the approved migration envelope): +/// `serde_json` rejects escaped lone surrogates (e.g. `"\ud800"`) and +/// out-of-range number literals (e.g. `1e400`, `Infinity` in JS) that JS +/// parses, so wrappers containing them are NOT unwrapped here — the content +/// falls through unchanged and later fails to encode (`encoded: None`), i.e. +/// the original payload is conservatively kept. +fn unwrap_tool_content(content: String) -> String { + let Ok(Value::Array(elements)) = serde_json::from_str(&content) else { + return content; + }; + let Some(Value::Object(mut first)) = elements.into_iter().next() else { + return content; + }; + let is_text_block = first.get("type").and_then(Value::as_str) == Some("text"); + match (is_text_block, first.remove("text")) { + (true, Some(Value::String(text))) => text, + _ => content, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn encode_one(raw_content: &str, unwrap: bool) -> ToonEncodeResult { + let results = toon_encode_tool_results(vec![ToonEncodeItem { + id: "t1".to_string(), + raw_content: raw_content.to_string(), + unwrap, + }]); + assert_eq!(results.len(), 1); + results.into_iter().next().expect("one result") + } + + const INNER: &str = r#"{"data":[{"id":1,"v":"a"},{"id":2,"v":"b"}],"ok":true}"#; + const INNER_TOON: &str = "data[2]{id,v}:\n 1,a\n 2,b\nok: true"; + + #[test] + fn unwrap_single_text_wrapper() { + let wrapped = serde_json::to_string(&serde_json::json!([{"type": "text", "text": INNER}])) + .expect("serialize fixture"); + let result = encode_one(&wrapped, true); + assert_eq!(result.normalized, INNER); + assert_eq!(result.encoded.as_deref(), Some(INNER_TOON)); + } + + #[test] + fn unwrap_multi_element_wrapper_uses_first_text_only() { + let wrapped = serde_json::to_string(&serde_json::json!([ + {"type": "text", "text": INNER}, + {"type": "text", "text": r#"{"second":"ignored"}"#}, + ])) + .expect("serialize fixture"); + let result = encode_one(&wrapped, true); + assert_eq!(result.normalized, INNER); + assert_eq!(result.encoded.as_deref(), Some(INNER_TOON)); + } + + #[test] + fn unwrap_first_block_not_text_returns_content_unchanged() { + let wrapped = serde_json::to_string(&serde_json::json!([ + {"type": "image", "url": "http://x"}, + {"type": "text", "text": INNER}, + ])) + .expect("serialize fixture"); + let result = encode_one(&wrapped, true); + // Not unwrapped: the whole wrapper array is what gets encoded. + assert_eq!(result.normalized, wrapped); + assert!(result.encoded.is_some()); + assert_ne!(result.encoded.as_deref(), Some(INNER_TOON)); + } + + #[test] + fn unwrap_text_field_not_a_string_returns_content_unchanged() { + let wrapped = r#"[{"type":"text","text":42}]"#; + let result = encode_one(wrapped, true); + assert_eq!(result.normalized, wrapped); + } + + #[test] + fn unwrap_non_array_json_returns_content_unchanged() { + let result = encode_one(INNER, true); + assert_eq!(result.normalized, INNER); + assert_eq!(result.encoded.as_deref(), Some(INNER_TOON)); + } + + #[test] + fn unwrap_empty_array_returns_content_unchanged() { + let result = encode_one("[]", true); + assert_eq!(result.normalized, "[]"); + assert!(result.encoded.is_some()); + } + + #[test] + fn unwrap_first_element_not_an_object_returns_content_unchanged() { + let raw = r#"["text",{"type":"text","text":"x"}]"#; + let result = encode_one(raw, true); + assert_eq!(result.normalized, raw); + } + + #[test] + fn unwrap_invalid_json_returns_content_unchanged() { + let raw = "not json at all"; + let result = encode_one(raw, true); + assert_eq!(result.normalized, raw); + assert_eq!(result.encoded, None); + } + + #[test] + fn unwrap_false_skips_unwrapping_even_for_wrapper_shape() { + let wrapped = serde_json::to_string(&serde_json::json!([{"type": "text", "text": INNER}])) + .expect("serialize fixture"); + let result = encode_one(&wrapped, false); + assert_eq!(result.normalized, wrapped); + assert_ne!(result.encoded.as_deref(), Some(INNER_TOON)); + } + + #[test] + fn wrapper_with_escaped_lone_surrogate_falls_through_unchanged() { + // JS `JSON.parse` accepts this wrapper and would unwrap it; serde_json + // rejects the escaped lone surrogate, so the content is kept as-is and + // nothing is encoded (approved JS→Rust migration divergence). + let wrapped = r#"[{"type":"text","text":"\ud800"}]"#; + let result = encode_one(wrapped, true); + assert_eq!(result.normalized, wrapped); + assert_eq!(result.encoded, None); + } + + #[test] + fn wrapper_containing_invalid_json_text_yields_no_encoding() { + let wrapped = r#"[{"type":"text","text":"plain prose result"}]"#; + let result = encode_one(wrapped, true); + assert_eq!(result.normalized, "plain prose result"); + assert_eq!(result.encoded, None); + } + + #[test] + fn malformed_json_yields_none_with_raw_normalized() { + for raw in [ + r#"{"a": [1, 2"#, + "Tool run 42 output", + "{'a': 1}", + r#"{"x": NaN}"#, + "", + ] { + let result = encode_one(raw, true); + assert_eq!(result.normalized, raw); + assert_eq!(result.encoded, None, "raw content: {raw:?}"); + } + } + + #[test] + fn non_object_roots_encode() { + for (raw, expected) in [ + ("42", "42"), + (r#""just a string""#, "just a string"), + ("true", "true"), + ("null", "null"), + ("[1,2,3,4,5]", "[5]: 1,2,3,4,5"), + ("{}", ""), + ("[]", "[0]:"), + ] { + let result = encode_one(raw, false); + assert_eq!(result.encoded.as_deref(), Some(expected), "raw: {raw}"); + } + } + + #[test] + fn boundary_numbers_encode_exactly() { + for (raw, expected) in [ + (r#"{"n":9007199254740991}"#, "n: 9007199254740991"), // 2^53 - 1 + (r#"{"n":9007199254740992}"#, "n: 9007199254740992"), // 2^53 + (r#"{"n":9007199254740993}"#, "n: 9007199254740993"), // 2^53 + 1 (JS would coerce) + (r#"{"n":9223372036854775807}"#, "n: 9223372036854775807"), // i64::MAX + (r#"{"n":-9223372036854775808}"#, "n: -9223372036854775808"), // i64::MIN + (r#"{"n":18446744073709551615}"#, "n: 18446744073709551615"), // u64::MAX + (r#"{"x":-0}"#, "x: 0"), + (r#"{"x":1e-7}"#, "x: 0.0000001"), + ] { + let result = encode_one(raw, true); + assert_eq!(result.encoded.as_deref(), Some(expected), "raw: {raw}"); + } + // 1e300 expands to the full decimal literal; pin its shape, not 300 zeros. + let huge = encode_one(r#"{"x":1e300}"#, true) + .encoded + .expect("1e300 encodes"); + assert!(huge.starts_with("x: 1")); + assert_eq!(huge.len(), "x: ".len() + 301); + } + + #[test] + fn batch_is_positional_and_same_length() { + let items = vec![ + ToonEncodeItem { + id: "a".to_string(), + raw_content: r#"{"a":1}"#.to_string(), + unwrap: true, + }, + ToonEncodeItem { + id: "a".to_string(), // duplicate id: results are positional + raw_content: "not json".to_string(), + unwrap: true, + }, + ToonEncodeItem { + id: "c".to_string(), + raw_content: r#"{"c":3}"#.to_string(), + unwrap: false, + }, + ]; + let results = toon_encode_tool_results(items); + assert_eq!(results.len(), 3); + assert_eq!(results[0].encoded.as_deref(), Some("a: 1")); + assert_eq!(results[1].encoded, None); + assert_eq!(results[2].encoded.as_deref(), Some("c: 3")); + } + + #[test] + fn empty_batch_returns_empty() { + assert!(toon_encode_tool_results(Vec::new()).is_empty()); + } +} diff --git a/platform/archestra-rs/proxy-transform-core/tests/fixtures/gen-corpus.mts b/platform/archestra-rs/proxy-transform-core/tests/fixtures/gen-corpus.mts new file mode 100644 index 00000000000..80e34bdcbf5 --- /dev/null +++ b/platform/archestra-rs/proxy-transform-core/tests/fixtures/gen-corpus.mts @@ -0,0 +1,191 @@ +// Golden-corpus INPUT generator (deterministic). Writes the `name`/`rawContent`/ +// `unwrap` fields of golden-corpus.json; the expected outputs are then filled in +// by the Rust side: +// 1. from platform/backend: +// pnpm exec tsx ../archestra-rs/proxy-transform-core/tests/fixtures/gen-corpus.mts +// 2. from platform/archestra-rs: +// UPDATE_TOON_GOLDENS=1 cargo test -p proxy_transform_core --test golden_corpus +// +// Raw contents for boundary numbers are hand-written strings so exact JSON +// literals survive (JS values would coerce before serialization). The bench +// items reuse the T0 benchmark corpus builder for realistic kernel shapes. +import { writeFileSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { buildBatch } from "../../../../backend/src/routes/proxy/__bench__/corpus"; + +interface CorpusItem { + name: string; + rawContent: string; + unwrap: boolean; +} + +const TOOL_RESULT_DATA = { + files: [ + { name: "README.md", size: 1024, type: "file" }, + { name: "src", size: 4096, type: "directory" }, + { name: "package.json", size: 512, type: "file" }, + { name: "tsconfig.json", size: 256, type: "file" }, + { name: "node_modules", size: 102400, type: "directory" }, + ], + totalCount: 5, + directory: ".", +}; + +const items: CorpusItem[] = []; +const add = (name: string, rawContent: string, unwrap = true) => + items.push({ name, rawContent, unwrap }); + +// --- ordering --- +add("order-integer-like-keys", '{"2":"two","10":"ten","a":1,"1":"one","b":{"20":true,"3":false,"x":null}}'); +add("order-array-of-objs-mixed-keys", '[{"10":1,"2":2,"z":3},{"10":4,"2":5,"z":6}]'); + +// --- boundary numbers --- +add("num-2p53-minus1", '{"n":9007199254740991}'); +add("num-2p53", '{"n":9007199254740992}'); +add("num-2p53-plus1", '{"n":9007199254740993}'); +add("num-neg-2p53-plus1", '{"n":-9007199254740993}'); +add("num-i64-max", '{"n":9223372036854775807}'); +add("num-i64-max-plus1", '{"n":9223372036854775808}'); +add("num-i64-min", '{"n":-9223372036854775808}'); +add("num-u64-max", '{"n":18446744073709551615}'); +add("num-u64-max-plus1", '{"n":18446744073709551616}'); +add("num-1e21-exp", '{"x":1e21}'); +add("num-1e21-expanded", '{"x":1000000000000000000000}'); +add("num-1e300", '{"x":1e300}'); +add("num-neg-1e21", '{"x":-1e21}'); +add("num-1e-7", '{"x":1e-7}'); +add("num-1e-300", '{"x":1e-300}'); +add("num-neg-zero", '{"x":-0}'); +add("num-neg-zero-float", '{"x":-0.0}'); +add("num-float-precision", '{"x":0.1,"y":1.005,"z":123.456e2}'); +add("num-large-neg", '{"x":-1.7976931348623157e308}'); + +// --- escaping / unicode --- +add("esc-quotes-commas-colons", '{"a":"he said \\"hi, there\\": ok","b":"comma,separated","c":"colon: value","d":"[bracket] {brace}"}'); +add("esc-control-chars", '{"a":"line1\\nline2\\ttabbed","b":"back\\\\slash","c":"nul\\u0000end","d":"\\u001b[31mred"}'); +add("esc-toon-specials", '{"a":" leading spaces","b":"trailing ","c":"#comment-ish","d":"- dash start","e":"|pipe|","f":"true","g":"123","h":"null","i":""}'); +add("unicode-mixed", '{"emoji":"🎉🚀 done","cjk":"日本語テスト","combining":"éé","rtl":"مرحبا","surrogate":"𝄞 music"}'); + +// --- nesting / roots --- +add("nested-deep", '{"a":{"b":{"c":{"d":{"e":[1,{"f":[true,null,{"g":"deep"}]}]}}}}}'); +add("nested-mixed-arrays", '[[1,2],[3,[4,5]],{"k":[{"a":1},{"a":2}]}]'); +add("root-string", '"just a string"'); +add("root-number", "42"); +add("root-float", "3.14"); +add("root-true", "true"); +add("root-null", "null"); +add("root-array-scalars", "[1,2,3,4,5]"); +add("root-array-strings", '["a","b","c with, comma"]'); +add("empty-object", "{}"); +add("empty-array", "[]"); +add("array-of-empty-objects", "[{},{},{}]"); // encode-only golden: toon-format issue #74 (decoder rejects `[N]{}:`) +add("array-with-one-empty-object", '[{"a":1},{}]'); + +// --- malformed (must be skipped, encoded=null) --- +add("malformed-truncated", '{"a": [1, 2'); +add("malformed-prose", "Tool run 42 output: alpha bravo charlie"); +add("malformed-single-quotes", "{'a': 1}"); +add("malformed-nan", '{"x": NaN}'); +add("malformed-infinity", '{"x": Infinity}'); +add("malformed-empty", ""); + +// --- wrappers --- +const inner = JSON.stringify({ data: [{ id: 1, v: "a" }, { id: 2, v: "b" }], ok: true }); +add("wrapped-single-text", JSON.stringify([{ type: "text", text: inner }])); +add("wrapped-multi-text", JSON.stringify([ + { type: "text", text: inner }, + { type: "text", text: '{"second":"element ignored by unwrap"}' }, +])); +add("wrapped-first-not-text", JSON.stringify([ + { type: "image", url: "http://x" }, + { type: "text", text: inner }, +])); +add("wrapped-text-not-json", JSON.stringify([{ type: "text", text: "plain prose result" }])); +add("wrapped-but-unwrap-false", JSON.stringify([{ type: "text", text: inner }]), false); + +// --- realistic provider payloads --- +add("provider-matrix-tool-result", JSON.stringify(TOOL_RESULT_DATA)); +add("bedrock-json-branch", JSON.stringify(TOOL_RESULT_DATA), false); +add("realistic-github-issues", JSON.stringify({ + items: Array.from({ length: 12 }, (_, i) => ({ + number: 100 + i, + title: `Issue title number ${i}: something broke, badly`, + state: i % 3 === 0 ? "closed" : "open", + user: { login: `user${i}`, id: 1000 + i }, + labels: [`bug`, `p${i % 3}`], + comments: i * 2, + created_at: `2026-06-${String(1 + i).padStart(2, "0")}T10:00:00Z`, + })), + total_count: 12, +})); +add("realistic-db-rows", JSON.stringify( + Array.from({ length: 20 }, (_, i) => ({ + id: i, + email: `person${i}@example.com`, + balance: Math.round((i * 137.13 % 1000) * 100) / 100, + active: i % 2 === 0, + region: ["us-east", "eu-west", "ap-south"][i % 3], + })), +)); + +// --- bench-corpus derived (realistic kernel shapes, deterministic seed) --- +for (const [i, benchItem] of buildBatch({ name: "1KB", payloadBytes: 1 << 10, count: 10 }, 42).entries()) { + items.push({ name: `bench-1kb-${i}`, rawContent: benchItem.rawContent, unwrap: benchItem.unwrap }); +} +for (const [i, benchItem] of buildBatch({ name: "10KB", payloadBytes: 10 << 10, count: 3 }, 7).entries()) { + items.push({ name: `bench-10kb-${i}`, rawContent: benchItem.rawContent, unwrap: benchItem.unwrap }); +} + +// --- near-boundary family: engineered so TOON savings hover around zero. +// Heterogeneous keys + comma-laden strings kill the tabular win; sweep sizes +// so some land within ±2 tokens of the original. +for (let k = 1; k <= 10; k++) { + const obj: Record = {}; + for (let j = 0; j < k; j++) { + obj[`key_${j}`] = j % 2 === 0 ? `val, with comma ${j}` : j * 1.5; + } + add(`boundary-obj-${k}`, JSON.stringify(obj)); +} +for (let k = 1; k <= 6; k++) { + const arr = Array.from({ length: k }, (_, j) => ({ + [`f${j}`]: `x,y ${j}`, + n: j, + })); + add(`boundary-hetero-arr-${k}`, JSON.stringify(arr)); +} + +// Hyphenated values are quoted by the v3 encoder ('-' is structural), so the +// token savings sweep crosses zero around these sizes — near-boundary coverage +// for keep/reject decisions. +for (let k = 1; k <= 8; k++) { + const arr = Array.from({ length: k }, (_, j) => ({ + sku: `AB-${100 + j}`, + zone: `us-east-${j}`, + n: j, + })); + add(`boundary-hyphen-arr-${k}`, JSON.stringify(arr)); +} + +// Finer sweep around the keep/reject crossing: 1-2 rows, mixing bare fields +// (savings source) and hyphenated fields (quoting penalty), varying counts so +// savings land at -1, 0, +1, +2 tokens. +for (let bare = 0; bare <= 4; bare++) { + for (let hyph = 1; hyph <= 3; hyph++) { + for (const rows of [1, 2]) { + const arr = Array.from({ length: rows }, (_, r) => { + const row: Record = {}; + for (let j = 0; j < bare; j++) row[`b${j}`] = `plain${r}${j}`; + for (let j = 0; j < hyph; j++) row[`h${j}`] = `us-east-${r}${j}`; + return row; + }); + add(`fine-r${rows}-b${bare}-h${hyph}`, JSON.stringify(arr)); + } + } +} + +const out = + process.argv[2] ?? + join(dirname(fileURLToPath(import.meta.url)), "golden-corpus.json"); +writeFileSync(out, `${JSON.stringify(items, null, 2)}\n`); +console.log(`wrote ${items.length} corpus items to ${out}`); diff --git a/platform/archestra-rs/proxy-transform-core/tests/fixtures/golden-corpus.json b/platform/archestra-rs/proxy-transform-core/tests/fixtures/golden-corpus.json new file mode 100644 index 00000000000..3d82c2cb5b1 --- /dev/null +++ b/platform/archestra-rs/proxy-transform-core/tests/fixtures/golden-corpus.json @@ -0,0 +1,1082 @@ +[ + { + "name": "order-integer-like-keys", + "rawContent": "{\"2\":\"two\",\"10\":\"ten\",\"a\":1,\"1\":\"one\",\"b\":{\"20\":true,\"3\":false,\"x\":null}}", + "unwrap": true, + "expected": { + "normalized": "{\"2\":\"two\",\"10\":\"ten\",\"a\":1,\"1\":\"one\",\"b\":{\"20\":true,\"3\":false,\"x\":null}}", + "encoded": "\"2\": two\n\"10\": ten\na: 1\n\"1\": one\nb:\n \"20\": true\n \"3\": false\n x: null" + } + }, + { + "name": "order-array-of-objs-mixed-keys", + "rawContent": "[{\"10\":1,\"2\":2,\"z\":3},{\"10\":4,\"2\":5,\"z\":6}]", + "unwrap": true, + "expected": { + "normalized": "[{\"10\":1,\"2\":2,\"z\":3},{\"10\":4,\"2\":5,\"z\":6}]", + "encoded": "[2]{\"10\",\"2\",z}:\n 1,2,3\n 4,5,6" + } + }, + { + "name": "num-2p53-minus1", + "rawContent": "{\"n\":9007199254740991}", + "unwrap": true, + "expected": { + "normalized": "{\"n\":9007199254740991}", + "encoded": "n: 9007199254740991" + } + }, + { + "name": "num-2p53", + "rawContent": "{\"n\":9007199254740992}", + "unwrap": true, + "expected": { + "normalized": "{\"n\":9007199254740992}", + "encoded": "n: 9007199254740992" + } + }, + { + "name": "num-2p53-plus1", + "rawContent": "{\"n\":9007199254740993}", + "unwrap": true, + "expected": { + "normalized": "{\"n\":9007199254740993}", + "encoded": "n: 9007199254740993" + } + }, + { + "name": "num-neg-2p53-plus1", + "rawContent": "{\"n\":-9007199254740993}", + "unwrap": true, + "expected": { + "normalized": "{\"n\":-9007199254740993}", + "encoded": "n: -9007199254740993" + } + }, + { + "name": "num-i64-max", + "rawContent": "{\"n\":9223372036854775807}", + "unwrap": true, + "expected": { + "normalized": "{\"n\":9223372036854775807}", + "encoded": "n: 9223372036854775807" + } + }, + { + "name": "num-i64-max-plus1", + "rawContent": "{\"n\":9223372036854775808}", + "unwrap": true, + "expected": { + "normalized": "{\"n\":9223372036854775808}", + "encoded": "n: 9223372036854775808" + } + }, + { + "name": "num-i64-min", + "rawContent": "{\"n\":-9223372036854775808}", + "unwrap": true, + "expected": { + "normalized": "{\"n\":-9223372036854775808}", + "encoded": "n: -9223372036854775808" + } + }, + { + "name": "num-u64-max", + "rawContent": "{\"n\":18446744073709551615}", + "unwrap": true, + "expected": { + "normalized": "{\"n\":18446744073709551615}", + "encoded": "n: 18446744073709551615" + } + }, + { + "name": "num-u64-max-plus1", + "rawContent": "{\"n\":18446744073709551616}", + "unwrap": true, + "expected": { + "normalized": "{\"n\":18446744073709551616}", + "encoded": "n: 18446744073709551615" + } + }, + { + "name": "num-1e21-exp", + "rawContent": "{\"x\":1e21}", + "unwrap": true, + "expected": { + "normalized": "{\"x\":1e21}", + "encoded": "x: 1000000000000000000000" + } + }, + { + "name": "num-1e21-expanded", + "rawContent": "{\"x\":1000000000000000000000}", + "unwrap": true, + "expected": { + "normalized": "{\"x\":1000000000000000000000}", + "encoded": "x: 1000000000000000000000" + } + }, + { + "name": "num-1e300", + "rawContent": "{\"x\":1e300}", + "unwrap": true, + "expected": { + "normalized": "{\"x\":1e300}", + "encoded": "x: 1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + } + }, + { + "name": "num-neg-1e21", + "rawContent": "{\"x\":-1e21}", + "unwrap": true, + "expected": { + "normalized": "{\"x\":-1e21}", + "encoded": "x: -1000000000000000000000" + } + }, + { + "name": "num-1e-7", + "rawContent": "{\"x\":1e-7}", + "unwrap": true, + "expected": { + "normalized": "{\"x\":1e-7}", + "encoded": "x: 0.0000001" + } + }, + { + "name": "num-1e-300", + "rawContent": "{\"x\":1e-300}", + "unwrap": true, + "expected": { + "normalized": "{\"x\":1e-300}", + "encoded": "x: 0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001" + } + }, + { + "name": "num-neg-zero", + "rawContent": "{\"x\":-0}", + "unwrap": true, + "expected": { + "normalized": "{\"x\":-0}", + "encoded": "x: 0" + } + }, + { + "name": "num-neg-zero-float", + "rawContent": "{\"x\":-0.0}", + "unwrap": true, + "expected": { + "normalized": "{\"x\":-0.0}", + "encoded": "x: 0" + } + }, + { + "name": "num-float-precision", + "rawContent": "{\"x\":0.1,\"y\":1.005,\"z\":123.456e2}", + "unwrap": true, + "expected": { + "normalized": "{\"x\":0.1,\"y\":1.005,\"z\":123.456e2}", + "encoded": "x: 0.1\ny: 1.005\nz: 12345.6" + } + }, + { + "name": "num-large-neg", + "rawContent": "{\"x\":-1.7976931348623157e308}", + "unwrap": true, + "expected": { + "normalized": "{\"x\":-1.7976931348623157e308}", + "encoded": "x: -179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + } + }, + { + "name": "esc-quotes-commas-colons", + "rawContent": "{\"a\":\"he said \\\"hi, there\\\": ok\",\"b\":\"comma,separated\",\"c\":\"colon: value\",\"d\":\"[bracket] {brace}\"}", + "unwrap": true, + "expected": { + "normalized": "{\"a\":\"he said \\\"hi, there\\\": ok\",\"b\":\"comma,separated\",\"c\":\"colon: value\",\"d\":\"[bracket] {brace}\"}", + "encoded": "a: \"he said \\\"hi, there\\\": ok\"\nb: \"comma,separated\"\nc: \"colon: value\"\nd: \"[bracket] {brace}\"" + } + }, + { + "name": "esc-control-chars", + "rawContent": "{\"a\":\"line1\\nline2\\ttabbed\",\"b\":\"back\\\\slash\",\"c\":\"nul\\u0000end\",\"d\":\"\\u001b[31mred\"}", + "unwrap": true, + "expected": { + "normalized": "{\"a\":\"line1\\nline2\\ttabbed\",\"b\":\"back\\\\slash\",\"c\":\"nul\\u0000end\",\"d\":\"\\u001b[31mred\"}", + "encoded": "a: \"line1\\nline2\\ttabbed\"\nb: \"back\\\\slash\"\nc: nul\u0000end\nd: \"\u001b[31mred\"" + } + }, + { + "name": "esc-toon-specials", + "rawContent": "{\"a\":\" leading spaces\",\"b\":\"trailing \",\"c\":\"#comment-ish\",\"d\":\"- dash start\",\"e\":\"|pipe|\",\"f\":\"true\",\"g\":\"123\",\"h\":\"null\",\"i\":\"\"}", + "unwrap": true, + "expected": { + "normalized": "{\"a\":\" leading spaces\",\"b\":\"trailing \",\"c\":\"#comment-ish\",\"d\":\"- dash start\",\"e\":\"|pipe|\",\"f\":\"true\",\"g\":\"123\",\"h\":\"null\",\"i\":\"\"}", + "encoded": "a: \" leading spaces\"\nb: \"trailing \"\nc: \"#comment-ish\"\nd: \"- dash start\"\ne: |pipe|\nf: \"true\"\ng: \"123\"\nh: \"null\"\ni: \"\"" + } + }, + { + "name": "unicode-mixed", + "rawContent": "{\"emoji\":\"🎉🚀 done\",\"cjk\":\"日本語テスト\",\"combining\":\"éé\",\"rtl\":\"مرحبا\",\"surrogate\":\"𝄞 music\"}", + "unwrap": true, + "expected": { + "normalized": "{\"emoji\":\"🎉🚀 done\",\"cjk\":\"日本語テスト\",\"combining\":\"éé\",\"rtl\":\"مرحبا\",\"surrogate\":\"𝄞 music\"}", + "encoded": "emoji: 🎉🚀 done\ncjk: 日本語テスト\ncombining: éé\nrtl: مرحبا\nsurrogate: 𝄞 music" + } + }, + { + "name": "nested-deep", + "rawContent": "{\"a\":{\"b\":{\"c\":{\"d\":{\"e\":[1,{\"f\":[true,null,{\"g\":\"deep\"}]}]}}}}}", + "unwrap": true, + "expected": { + "normalized": "{\"a\":{\"b\":{\"c\":{\"d\":{\"e\":[1,{\"f\":[true,null,{\"g\":\"deep\"}]}]}}}}}", + "encoded": "a:\n b:\n c:\n d:\n e[2]:\n - 1\n - f[3]:\n - true\n - null\n - g: deep" + } + }, + { + "name": "nested-mixed-arrays", + "rawContent": "[[1,2],[3,[4,5]],{\"k\":[{\"a\":1},{\"a\":2}]}]", + "unwrap": true, + "expected": { + "normalized": "[[1,2],[3,[4,5]],{\"k\":[{\"a\":1},{\"a\":2}]}]", + "encoded": "[3]:\n - [2]: 1,2\n - [2]:\n - 3\n - [2]: 4,5\n - k[2]{a}:\n 1\n 2" + } + }, + { + "name": "root-string", + "rawContent": "\"just a string\"", + "unwrap": true, + "expected": { + "normalized": "\"just a string\"", + "encoded": "just a string" + } + }, + { + "name": "root-number", + "rawContent": "42", + "unwrap": true, + "expected": { + "normalized": "42", + "encoded": "42" + } + }, + { + "name": "root-float", + "rawContent": "3.14", + "unwrap": true, + "expected": { + "normalized": "3.14", + "encoded": "3.14" + } + }, + { + "name": "root-true", + "rawContent": "true", + "unwrap": true, + "expected": { + "normalized": "true", + "encoded": "true" + } + }, + { + "name": "root-null", + "rawContent": "null", + "unwrap": true, + "expected": { + "normalized": "null", + "encoded": "null" + } + }, + { + "name": "root-array-scalars", + "rawContent": "[1,2,3,4,5]", + "unwrap": true, + "expected": { + "normalized": "[1,2,3,4,5]", + "encoded": "[5]: 1,2,3,4,5" + } + }, + { + "name": "root-array-strings", + "rawContent": "[\"a\",\"b\",\"c with, comma\"]", + "unwrap": true, + "expected": { + "normalized": "[\"a\",\"b\",\"c with, comma\"]", + "encoded": "[3]: a,b,\"c with, comma\"" + } + }, + { + "name": "empty-object", + "rawContent": "{}", + "unwrap": true, + "expected": { + "normalized": "{}", + "encoded": "" + } + }, + { + "name": "empty-array", + "rawContent": "[]", + "unwrap": true, + "expected": { + "normalized": "[]", + "encoded": "[0]:" + } + }, + { + "name": "array-of-empty-objects", + "rawContent": "[{},{},{}]", + "unwrap": true, + "expected": { + "normalized": "[{},{},{}]", + "encoded": "[3]{}:\n \n \n " + } + }, + { + "name": "array-with-one-empty-object", + "rawContent": "[{\"a\":1},{}]", + "unwrap": true, + "expected": { + "normalized": "[{\"a\":1},{}]", + "encoded": "[2]:\n - a: 1\n -" + } + }, + { + "name": "malformed-truncated", + "rawContent": "{\"a\": [1, 2", + "unwrap": true, + "expected": { + "normalized": "{\"a\": [1, 2", + "encoded": null + } + }, + { + "name": "malformed-prose", + "rawContent": "Tool run 42 output: alpha bravo charlie", + "unwrap": true, + "expected": { + "normalized": "Tool run 42 output: alpha bravo charlie", + "encoded": null + } + }, + { + "name": "malformed-single-quotes", + "rawContent": "{'a': 1}", + "unwrap": true, + "expected": { + "normalized": "{'a': 1}", + "encoded": null + } + }, + { + "name": "malformed-nan", + "rawContent": "{\"x\": NaN}", + "unwrap": true, + "expected": { + "normalized": "{\"x\": NaN}", + "encoded": null + } + }, + { + "name": "malformed-infinity", + "rawContent": "{\"x\": Infinity}", + "unwrap": true, + "expected": { + "normalized": "{\"x\": Infinity}", + "encoded": null + } + }, + { + "name": "malformed-empty", + "rawContent": "", + "unwrap": true, + "expected": { + "normalized": "", + "encoded": null + } + }, + { + "name": "wrapped-single-text", + "rawContent": "[{\"type\":\"text\",\"text\":\"{\\\"data\\\":[{\\\"id\\\":1,\\\"v\\\":\\\"a\\\"},{\\\"id\\\":2,\\\"v\\\":\\\"b\\\"}],\\\"ok\\\":true}\"}]", + "unwrap": true, + "expected": { + "normalized": "{\"data\":[{\"id\":1,\"v\":\"a\"},{\"id\":2,\"v\":\"b\"}],\"ok\":true}", + "encoded": "data[2]{id,v}:\n 1,a\n 2,b\nok: true" + } + }, + { + "name": "wrapped-multi-text", + "rawContent": "[{\"type\":\"text\",\"text\":\"{\\\"data\\\":[{\\\"id\\\":1,\\\"v\\\":\\\"a\\\"},{\\\"id\\\":2,\\\"v\\\":\\\"b\\\"}],\\\"ok\\\":true}\"},{\"type\":\"text\",\"text\":\"{\\\"second\\\":\\\"element ignored by unwrap\\\"}\"}]", + "unwrap": true, + "expected": { + "normalized": "{\"data\":[{\"id\":1,\"v\":\"a\"},{\"id\":2,\"v\":\"b\"}],\"ok\":true}", + "encoded": "data[2]{id,v}:\n 1,a\n 2,b\nok: true" + } + }, + { + "name": "wrapped-first-not-text", + "rawContent": "[{\"type\":\"image\",\"url\":\"http://x\"},{\"type\":\"text\",\"text\":\"{\\\"data\\\":[{\\\"id\\\":1,\\\"v\\\":\\\"a\\\"},{\\\"id\\\":2,\\\"v\\\":\\\"b\\\"}],\\\"ok\\\":true}\"}]", + "unwrap": true, + "expected": { + "normalized": "[{\"type\":\"image\",\"url\":\"http://x\"},{\"type\":\"text\",\"text\":\"{\\\"data\\\":[{\\\"id\\\":1,\\\"v\\\":\\\"a\\\"},{\\\"id\\\":2,\\\"v\\\":\\\"b\\\"}],\\\"ok\\\":true}\"}]", + "encoded": "[2]:\n - type: image\n url: \"http://x\"\n - type: text\n text: \"{\\\"data\\\":[{\\\"id\\\":1,\\\"v\\\":\\\"a\\\"},{\\\"id\\\":2,\\\"v\\\":\\\"b\\\"}],\\\"ok\\\":true}\"" + } + }, + { + "name": "wrapped-text-not-json", + "rawContent": "[{\"type\":\"text\",\"text\":\"plain prose result\"}]", + "unwrap": true, + "expected": { + "normalized": "plain prose result", + "encoded": null + } + }, + { + "name": "wrapped-but-unwrap-false", + "rawContent": "[{\"type\":\"text\",\"text\":\"{\\\"data\\\":[{\\\"id\\\":1,\\\"v\\\":\\\"a\\\"},{\\\"id\\\":2,\\\"v\\\":\\\"b\\\"}],\\\"ok\\\":true}\"}]", + "unwrap": false, + "expected": { + "normalized": "[{\"type\":\"text\",\"text\":\"{\\\"data\\\":[{\\\"id\\\":1,\\\"v\\\":\\\"a\\\"},{\\\"id\\\":2,\\\"v\\\":\\\"b\\\"}],\\\"ok\\\":true}\"}]", + "encoded": "[1]{type,text}:\n text,\"{\\\"data\\\":[{\\\"id\\\":1,\\\"v\\\":\\\"a\\\"},{\\\"id\\\":2,\\\"v\\\":\\\"b\\\"}],\\\"ok\\\":true}\"" + } + }, + { + "name": "provider-matrix-tool-result", + "rawContent": "{\"files\":[{\"name\":\"README.md\",\"size\":1024,\"type\":\"file\"},{\"name\":\"src\",\"size\":4096,\"type\":\"directory\"},{\"name\":\"package.json\",\"size\":512,\"type\":\"file\"},{\"name\":\"tsconfig.json\",\"size\":256,\"type\":\"file\"},{\"name\":\"node_modules\",\"size\":102400,\"type\":\"directory\"}],\"totalCount\":5,\"directory\":\".\"}", + "unwrap": true, + "expected": { + "normalized": "{\"files\":[{\"name\":\"README.md\",\"size\":1024,\"type\":\"file\"},{\"name\":\"src\",\"size\":4096,\"type\":\"directory\"},{\"name\":\"package.json\",\"size\":512,\"type\":\"file\"},{\"name\":\"tsconfig.json\",\"size\":256,\"type\":\"file\"},{\"name\":\"node_modules\",\"size\":102400,\"type\":\"directory\"}],\"totalCount\":5,\"directory\":\".\"}", + "encoded": "files[5]{name,size,type}:\n README.md,1024,file\n src,4096,directory\n package.json,512,file\n tsconfig.json,256,file\n node_modules,102400,directory\ntotalCount: 5\ndirectory: ." + } + }, + { + "name": "bedrock-json-branch", + "rawContent": "{\"files\":[{\"name\":\"README.md\",\"size\":1024,\"type\":\"file\"},{\"name\":\"src\",\"size\":4096,\"type\":\"directory\"},{\"name\":\"package.json\",\"size\":512,\"type\":\"file\"},{\"name\":\"tsconfig.json\",\"size\":256,\"type\":\"file\"},{\"name\":\"node_modules\",\"size\":102400,\"type\":\"directory\"}],\"totalCount\":5,\"directory\":\".\"}", + "unwrap": false, + "expected": { + "normalized": "{\"files\":[{\"name\":\"README.md\",\"size\":1024,\"type\":\"file\"},{\"name\":\"src\",\"size\":4096,\"type\":\"directory\"},{\"name\":\"package.json\",\"size\":512,\"type\":\"file\"},{\"name\":\"tsconfig.json\",\"size\":256,\"type\":\"file\"},{\"name\":\"node_modules\",\"size\":102400,\"type\":\"directory\"}],\"totalCount\":5,\"directory\":\".\"}", + "encoded": "files[5]{name,size,type}:\n README.md,1024,file\n src,4096,directory\n package.json,512,file\n tsconfig.json,256,file\n node_modules,102400,directory\ntotalCount: 5\ndirectory: ." + } + }, + { + "name": "realistic-github-issues", + "rawContent": "{\"items\":[{\"number\":100,\"title\":\"Issue title number 0: something broke, badly\",\"state\":\"closed\",\"user\":{\"login\":\"user0\",\"id\":1000},\"labels\":[\"bug\",\"p0\"],\"comments\":0,\"created_at\":\"2026-06-01T10:00:00Z\"},{\"number\":101,\"title\":\"Issue title number 1: something broke, badly\",\"state\":\"open\",\"user\":{\"login\":\"user1\",\"id\":1001},\"labels\":[\"bug\",\"p1\"],\"comments\":2,\"created_at\":\"2026-06-02T10:00:00Z\"},{\"number\":102,\"title\":\"Issue title number 2: something broke, badly\",\"state\":\"open\",\"user\":{\"login\":\"user2\",\"id\":1002},\"labels\":[\"bug\",\"p2\"],\"comments\":4,\"created_at\":\"2026-06-03T10:00:00Z\"},{\"number\":103,\"title\":\"Issue title number 3: something broke, badly\",\"state\":\"closed\",\"user\":{\"login\":\"user3\",\"id\":1003},\"labels\":[\"bug\",\"p0\"],\"comments\":6,\"created_at\":\"2026-06-04T10:00:00Z\"},{\"number\":104,\"title\":\"Issue title number 4: something broke, badly\",\"state\":\"open\",\"user\":{\"login\":\"user4\",\"id\":1004},\"labels\":[\"bug\",\"p1\"],\"comments\":8,\"created_at\":\"2026-06-05T10:00:00Z\"},{\"number\":105,\"title\":\"Issue title number 5: something broke, badly\",\"state\":\"open\",\"user\":{\"login\":\"user5\",\"id\":1005},\"labels\":[\"bug\",\"p2\"],\"comments\":10,\"created_at\":\"2026-06-06T10:00:00Z\"},{\"number\":106,\"title\":\"Issue title number 6: something broke, badly\",\"state\":\"closed\",\"user\":{\"login\":\"user6\",\"id\":1006},\"labels\":[\"bug\",\"p0\"],\"comments\":12,\"created_at\":\"2026-06-07T10:00:00Z\"},{\"number\":107,\"title\":\"Issue title number 7: something broke, badly\",\"state\":\"open\",\"user\":{\"login\":\"user7\",\"id\":1007},\"labels\":[\"bug\",\"p1\"],\"comments\":14,\"created_at\":\"2026-06-08T10:00:00Z\"},{\"number\":108,\"title\":\"Issue title number 8: something broke, badly\",\"state\":\"open\",\"user\":{\"login\":\"user8\",\"id\":1008},\"labels\":[\"bug\",\"p2\"],\"comments\":16,\"created_at\":\"2026-06-09T10:00:00Z\"},{\"number\":109,\"title\":\"Issue title number 9: something broke, badly\",\"state\":\"closed\",\"user\":{\"login\":\"user9\",\"id\":1009},\"labels\":[\"bug\",\"p0\"],\"comments\":18,\"created_at\":\"2026-06-10T10:00:00Z\"},{\"number\":110,\"title\":\"Issue title number 10: something broke, badly\",\"state\":\"open\",\"user\":{\"login\":\"user10\",\"id\":1010},\"labels\":[\"bug\",\"p1\"],\"comments\":20,\"created_at\":\"2026-06-11T10:00:00Z\"},{\"number\":111,\"title\":\"Issue title number 11: something broke, badly\",\"state\":\"open\",\"user\":{\"login\":\"user11\",\"id\":1011},\"labels\":[\"bug\",\"p2\"],\"comments\":22,\"created_at\":\"2026-06-12T10:00:00Z\"}],\"total_count\":12}", + "unwrap": true, + "expected": { + "normalized": "{\"items\":[{\"number\":100,\"title\":\"Issue title number 0: something broke, badly\",\"state\":\"closed\",\"user\":{\"login\":\"user0\",\"id\":1000},\"labels\":[\"bug\",\"p0\"],\"comments\":0,\"created_at\":\"2026-06-01T10:00:00Z\"},{\"number\":101,\"title\":\"Issue title number 1: something broke, badly\",\"state\":\"open\",\"user\":{\"login\":\"user1\",\"id\":1001},\"labels\":[\"bug\",\"p1\"],\"comments\":2,\"created_at\":\"2026-06-02T10:00:00Z\"},{\"number\":102,\"title\":\"Issue title number 2: something broke, badly\",\"state\":\"open\",\"user\":{\"login\":\"user2\",\"id\":1002},\"labels\":[\"bug\",\"p2\"],\"comments\":4,\"created_at\":\"2026-06-03T10:00:00Z\"},{\"number\":103,\"title\":\"Issue title number 3: something broke, badly\",\"state\":\"closed\",\"user\":{\"login\":\"user3\",\"id\":1003},\"labels\":[\"bug\",\"p0\"],\"comments\":6,\"created_at\":\"2026-06-04T10:00:00Z\"},{\"number\":104,\"title\":\"Issue title number 4: something broke, badly\",\"state\":\"open\",\"user\":{\"login\":\"user4\",\"id\":1004},\"labels\":[\"bug\",\"p1\"],\"comments\":8,\"created_at\":\"2026-06-05T10:00:00Z\"},{\"number\":105,\"title\":\"Issue title number 5: something broke, badly\",\"state\":\"open\",\"user\":{\"login\":\"user5\",\"id\":1005},\"labels\":[\"bug\",\"p2\"],\"comments\":10,\"created_at\":\"2026-06-06T10:00:00Z\"},{\"number\":106,\"title\":\"Issue title number 6: something broke, badly\",\"state\":\"closed\",\"user\":{\"login\":\"user6\",\"id\":1006},\"labels\":[\"bug\",\"p0\"],\"comments\":12,\"created_at\":\"2026-06-07T10:00:00Z\"},{\"number\":107,\"title\":\"Issue title number 7: something broke, badly\",\"state\":\"open\",\"user\":{\"login\":\"user7\",\"id\":1007},\"labels\":[\"bug\",\"p1\"],\"comments\":14,\"created_at\":\"2026-06-08T10:00:00Z\"},{\"number\":108,\"title\":\"Issue title number 8: something broke, badly\",\"state\":\"open\",\"user\":{\"login\":\"user8\",\"id\":1008},\"labels\":[\"bug\",\"p2\"],\"comments\":16,\"created_at\":\"2026-06-09T10:00:00Z\"},{\"number\":109,\"title\":\"Issue title number 9: something broke, badly\",\"state\":\"closed\",\"user\":{\"login\":\"user9\",\"id\":1009},\"labels\":[\"bug\",\"p0\"],\"comments\":18,\"created_at\":\"2026-06-10T10:00:00Z\"},{\"number\":110,\"title\":\"Issue title number 10: something broke, badly\",\"state\":\"open\",\"user\":{\"login\":\"user10\",\"id\":1010},\"labels\":[\"bug\",\"p1\"],\"comments\":20,\"created_at\":\"2026-06-11T10:00:00Z\"},{\"number\":111,\"title\":\"Issue title number 11: something broke, badly\",\"state\":\"open\",\"user\":{\"login\":\"user11\",\"id\":1011},\"labels\":[\"bug\",\"p2\"],\"comments\":22,\"created_at\":\"2026-06-12T10:00:00Z\"}],\"total_count\":12}", + "encoded": "items[12]:\n - number: 100\n title: \"Issue title number 0: something broke, badly\"\n state: closed\n user:\n login: user0\n id: 1000\n labels[2]: bug,p0\n comments: 0\n created_at: \"2026-06-01T10:00:00Z\"\n - number: 101\n title: \"Issue title number 1: something broke, badly\"\n state: open\n user:\n login: user1\n id: 1001\n labels[2]: bug,p1\n comments: 2\n created_at: \"2026-06-02T10:00:00Z\"\n - number: 102\n title: \"Issue title number 2: something broke, badly\"\n state: open\n user:\n login: user2\n id: 1002\n labels[2]: bug,p2\n comments: 4\n created_at: \"2026-06-03T10:00:00Z\"\n - number: 103\n title: \"Issue title number 3: something broke, badly\"\n state: closed\n user:\n login: user3\n id: 1003\n labels[2]: bug,p0\n comments: 6\n created_at: \"2026-06-04T10:00:00Z\"\n - number: 104\n title: \"Issue title number 4: something broke, badly\"\n state: open\n user:\n login: user4\n id: 1004\n labels[2]: bug,p1\n comments: 8\n created_at: \"2026-06-05T10:00:00Z\"\n - number: 105\n title: \"Issue title number 5: something broke, badly\"\n state: open\n user:\n login: user5\n id: 1005\n labels[2]: bug,p2\n comments: 10\n created_at: \"2026-06-06T10:00:00Z\"\n - number: 106\n title: \"Issue title number 6: something broke, badly\"\n state: closed\n user:\n login: user6\n id: 1006\n labels[2]: bug,p0\n comments: 12\n created_at: \"2026-06-07T10:00:00Z\"\n - number: 107\n title: \"Issue title number 7: something broke, badly\"\n state: open\n user:\n login: user7\n id: 1007\n labels[2]: bug,p1\n comments: 14\n created_at: \"2026-06-08T10:00:00Z\"\n - number: 108\n title: \"Issue title number 8: something broke, badly\"\n state: open\n user:\n login: user8\n id: 1008\n labels[2]: bug,p2\n comments: 16\n created_at: \"2026-06-09T10:00:00Z\"\n - number: 109\n title: \"Issue title number 9: something broke, badly\"\n state: closed\n user:\n login: user9\n id: 1009\n labels[2]: bug,p0\n comments: 18\n created_at: \"2026-06-10T10:00:00Z\"\n - number: 110\n title: \"Issue title number 10: something broke, badly\"\n state: open\n user:\n login: user10\n id: 1010\n labels[2]: bug,p1\n comments: 20\n created_at: \"2026-06-11T10:00:00Z\"\n - number: 111\n title: \"Issue title number 11: something broke, badly\"\n state: open\n user:\n login: user11\n id: 1011\n labels[2]: bug,p2\n comments: 22\n created_at: \"2026-06-12T10:00:00Z\"\ntotal_count: 12" + } + }, + { + "name": "realistic-db-rows", + "rawContent": "[{\"id\":0,\"email\":\"person0@example.com\",\"balance\":0,\"active\":true,\"region\":\"us-east\"},{\"id\":1,\"email\":\"person1@example.com\",\"balance\":137.13,\"active\":false,\"region\":\"eu-west\"},{\"id\":2,\"email\":\"person2@example.com\",\"balance\":274.26,\"active\":true,\"region\":\"ap-south\"},{\"id\":3,\"email\":\"person3@example.com\",\"balance\":411.39,\"active\":false,\"region\":\"us-east\"},{\"id\":4,\"email\":\"person4@example.com\",\"balance\":548.52,\"active\":true,\"region\":\"eu-west\"},{\"id\":5,\"email\":\"person5@example.com\",\"balance\":685.65,\"active\":false,\"region\":\"ap-south\"},{\"id\":6,\"email\":\"person6@example.com\",\"balance\":822.78,\"active\":true,\"region\":\"us-east\"},{\"id\":7,\"email\":\"person7@example.com\",\"balance\":959.91,\"active\":false,\"region\":\"eu-west\"},{\"id\":8,\"email\":\"person8@example.com\",\"balance\":97.04,\"active\":true,\"region\":\"ap-south\"},{\"id\":9,\"email\":\"person9@example.com\",\"balance\":234.17,\"active\":false,\"region\":\"us-east\"},{\"id\":10,\"email\":\"person10@example.com\",\"balance\":371.3,\"active\":true,\"region\":\"eu-west\"},{\"id\":11,\"email\":\"person11@example.com\",\"balance\":508.43,\"active\":false,\"region\":\"ap-south\"},{\"id\":12,\"email\":\"person12@example.com\",\"balance\":645.56,\"active\":true,\"region\":\"us-east\"},{\"id\":13,\"email\":\"person13@example.com\",\"balance\":782.69,\"active\":false,\"region\":\"eu-west\"},{\"id\":14,\"email\":\"person14@example.com\",\"balance\":919.82,\"active\":true,\"region\":\"ap-south\"},{\"id\":15,\"email\":\"person15@example.com\",\"balance\":56.95,\"active\":false,\"region\":\"us-east\"},{\"id\":16,\"email\":\"person16@example.com\",\"balance\":194.08,\"active\":true,\"region\":\"eu-west\"},{\"id\":17,\"email\":\"person17@example.com\",\"balance\":331.21,\"active\":false,\"region\":\"ap-south\"},{\"id\":18,\"email\":\"person18@example.com\",\"balance\":468.34,\"active\":true,\"region\":\"us-east\"},{\"id\":19,\"email\":\"person19@example.com\",\"balance\":605.47,\"active\":false,\"region\":\"eu-west\"}]", + "unwrap": true, + "expected": { + "normalized": "[{\"id\":0,\"email\":\"person0@example.com\",\"balance\":0,\"active\":true,\"region\":\"us-east\"},{\"id\":1,\"email\":\"person1@example.com\",\"balance\":137.13,\"active\":false,\"region\":\"eu-west\"},{\"id\":2,\"email\":\"person2@example.com\",\"balance\":274.26,\"active\":true,\"region\":\"ap-south\"},{\"id\":3,\"email\":\"person3@example.com\",\"balance\":411.39,\"active\":false,\"region\":\"us-east\"},{\"id\":4,\"email\":\"person4@example.com\",\"balance\":548.52,\"active\":true,\"region\":\"eu-west\"},{\"id\":5,\"email\":\"person5@example.com\",\"balance\":685.65,\"active\":false,\"region\":\"ap-south\"},{\"id\":6,\"email\":\"person6@example.com\",\"balance\":822.78,\"active\":true,\"region\":\"us-east\"},{\"id\":7,\"email\":\"person7@example.com\",\"balance\":959.91,\"active\":false,\"region\":\"eu-west\"},{\"id\":8,\"email\":\"person8@example.com\",\"balance\":97.04,\"active\":true,\"region\":\"ap-south\"},{\"id\":9,\"email\":\"person9@example.com\",\"balance\":234.17,\"active\":false,\"region\":\"us-east\"},{\"id\":10,\"email\":\"person10@example.com\",\"balance\":371.3,\"active\":true,\"region\":\"eu-west\"},{\"id\":11,\"email\":\"person11@example.com\",\"balance\":508.43,\"active\":false,\"region\":\"ap-south\"},{\"id\":12,\"email\":\"person12@example.com\",\"balance\":645.56,\"active\":true,\"region\":\"us-east\"},{\"id\":13,\"email\":\"person13@example.com\",\"balance\":782.69,\"active\":false,\"region\":\"eu-west\"},{\"id\":14,\"email\":\"person14@example.com\",\"balance\":919.82,\"active\":true,\"region\":\"ap-south\"},{\"id\":15,\"email\":\"person15@example.com\",\"balance\":56.95,\"active\":false,\"region\":\"us-east\"},{\"id\":16,\"email\":\"person16@example.com\",\"balance\":194.08,\"active\":true,\"region\":\"eu-west\"},{\"id\":17,\"email\":\"person17@example.com\",\"balance\":331.21,\"active\":false,\"region\":\"ap-south\"},{\"id\":18,\"email\":\"person18@example.com\",\"balance\":468.34,\"active\":true,\"region\":\"us-east\"},{\"id\":19,\"email\":\"person19@example.com\",\"balance\":605.47,\"active\":false,\"region\":\"eu-west\"}]", + "encoded": "[20]{id,email,balance,active,region}:\n 0,person0@example.com,0,true,\"us-east\"\n 1,person1@example.com,137.13,false,\"eu-west\"\n 2,person2@example.com,274.26,true,\"ap-south\"\n 3,person3@example.com,411.39,false,\"us-east\"\n 4,person4@example.com,548.52,true,\"eu-west\"\n 5,person5@example.com,685.65,false,\"ap-south\"\n 6,person6@example.com,822.78,true,\"us-east\"\n 7,person7@example.com,959.91,false,\"eu-west\"\n 8,person8@example.com,97.04,true,\"ap-south\"\n 9,person9@example.com,234.17,false,\"us-east\"\n 10,person10@example.com,371.3,true,\"eu-west\"\n 11,person11@example.com,508.43,false,\"ap-south\"\n 12,person12@example.com,645.56,true,\"us-east\"\n 13,person13@example.com,782.69,false,\"eu-west\"\n 14,person14@example.com,919.82,true,\"ap-south\"\n 15,person15@example.com,56.95,false,\"us-east\"\n 16,person16@example.com,194.08,true,\"eu-west\"\n 17,person17@example.com,331.21,false,\"ap-south\"\n 18,person18@example.com,468.34,true,\"us-east\"\n 19,person19@example.com,605.47,false,\"eu-west\"" + } + }, + { + "name": "bench-1kb-0", + "rawContent": "[{\"id\":0,\"sku\":\"SKU-601103\",\"name\":\"kilo uniform\",\"status\":\"archived\",\"score\":17.48,\"quantity\":263,\"active\":false,\"updatedAt\":\"2026-04-25T12:00:00Z\"},{\"id\":1,\"sku\":\"SKU-472317\",\"name\":\"foxtrot victor\",\"status\":\"archived\",\"score\":30.7,\"quantity\":98,\"active\":true,\"updatedAt\":\"2026-05-18T12:00:00Z\"},{\"id\":2,\"sku\":\"SKU-3842\",\"name\":\"lima uniform\",\"status\":\"active\",\"score\":59.23,\"quantity\":15,\"active\":false,\"updatedAt\":\"2026-01-06T12:00:00Z\"},{\"id\":3,\"sku\":\"SKU-783547\",\"name\":\"mike alpha\",\"status\":\"active\",\"score\":84.27,\"quantity\":243,\"active\":true,\"updatedAt\":\"2026-02-13T12:00:00Z\"},{\"id\":4,\"sku\":\"SKU-37439\",\"name\":\"bravo november\",\"status\":\"archived\",\"score\":24.52,\"quantity\":322,\"active\":false,\"updatedAt\":\"2026-02-21T12:00:00Z\"},{\"id\":5,\"sku\":\"SKU-858710\",\"name\":\"mike echo\",\"status\":\"pending\",\"score\":29.3,\"quantity\":37,\"active\":true,\"updatedAt\":\"2026-05-20T12:00:00Z\"},{\"id\":6,\"sku\":\"SKU-927839\",\"name\":\"charlie whiskey\",\"status\":\"pending\",\"score\":94.23,\"quantity\":68,\"active\":false,\"updatedAt\":\"2026-01-11T12:00:00Z\"}]", + "unwrap": true, + "expected": { + "normalized": "[{\"id\":0,\"sku\":\"SKU-601103\",\"name\":\"kilo uniform\",\"status\":\"archived\",\"score\":17.48,\"quantity\":263,\"active\":false,\"updatedAt\":\"2026-04-25T12:00:00Z\"},{\"id\":1,\"sku\":\"SKU-472317\",\"name\":\"foxtrot victor\",\"status\":\"archived\",\"score\":30.7,\"quantity\":98,\"active\":true,\"updatedAt\":\"2026-05-18T12:00:00Z\"},{\"id\":2,\"sku\":\"SKU-3842\",\"name\":\"lima uniform\",\"status\":\"active\",\"score\":59.23,\"quantity\":15,\"active\":false,\"updatedAt\":\"2026-01-06T12:00:00Z\"},{\"id\":3,\"sku\":\"SKU-783547\",\"name\":\"mike alpha\",\"status\":\"active\",\"score\":84.27,\"quantity\":243,\"active\":true,\"updatedAt\":\"2026-02-13T12:00:00Z\"},{\"id\":4,\"sku\":\"SKU-37439\",\"name\":\"bravo november\",\"status\":\"archived\",\"score\":24.52,\"quantity\":322,\"active\":false,\"updatedAt\":\"2026-02-21T12:00:00Z\"},{\"id\":5,\"sku\":\"SKU-858710\",\"name\":\"mike echo\",\"status\":\"pending\",\"score\":29.3,\"quantity\":37,\"active\":true,\"updatedAt\":\"2026-05-20T12:00:00Z\"},{\"id\":6,\"sku\":\"SKU-927839\",\"name\":\"charlie whiskey\",\"status\":\"pending\",\"score\":94.23,\"quantity\":68,\"active\":false,\"updatedAt\":\"2026-01-11T12:00:00Z\"}]", + "encoded": "[7]{id,sku,name,status,score,quantity,active,updatedAt}:\n 0,\"SKU-601103\",kilo uniform,archived,17.48,263,false,\"2026-04-25T12:00:00Z\"\n 1,\"SKU-472317\",foxtrot victor,archived,30.7,98,true,\"2026-05-18T12:00:00Z\"\n 2,\"SKU-3842\",lima uniform,active,59.23,15,false,\"2026-01-06T12:00:00Z\"\n 3,\"SKU-783547\",mike alpha,active,84.27,243,true,\"2026-02-13T12:00:00Z\"\n 4,\"SKU-37439\",bravo november,archived,24.52,322,false,\"2026-02-21T12:00:00Z\"\n 5,\"SKU-858710\",mike echo,pending,29.3,37,true,\"2026-05-20T12:00:00Z\"\n 6,\"SKU-927839\",charlie whiskey,pending,94.23,68,false,\"2026-01-11T12:00:00Z\"" + } + }, + { + "name": "bench-1kb-1", + "rawContent": "[{\"id\":0,\"sku\":\"SKU-481722\",\"name\":\"oscar victor\",\"status\":\"active\",\"score\":71.96,\"quantity\":467,\"active\":false,\"updatedAt\":\"2026-05-06T12:00:00Z\"},{\"id\":1,\"sku\":\"SKU-520316\",\"name\":\"tango charlie\",\"status\":\"failed\",\"score\":56.28,\"quantity\":458,\"active\":false,\"updatedAt\":\"2026-04-10T12:00:00Z\"},{\"id\":2,\"sku\":\"SKU-596239\",\"name\":\"hotel romeo\",\"status\":\"pending\",\"score\":44.98,\"quantity\":421,\"active\":true,\"updatedAt\":\"2026-06-25T12:00:00Z\"},{\"id\":3,\"sku\":\"SKU-431891\",\"name\":\"november hotel\",\"status\":\"active\",\"score\":69.67,\"quantity\":156,\"active\":true,\"updatedAt\":\"2026-06-03T12:00:00Z\"},{\"id\":4,\"sku\":\"SKU-475391\",\"name\":\"tango delta\",\"status\":\"failed\",\"score\":17.84,\"quantity\":354,\"active\":true,\"updatedAt\":\"2026-02-12T12:00:00Z\"},{\"id\":5,\"sku\":\"SKU-163437\",\"name\":\"papa india\",\"status\":\"active\",\"score\":62.89,\"quantity\":443,\"active\":false,\"updatedAt\":\"2026-05-12T12:00:00Z\"},{\"id\":6,\"sku\":\"SKU-131392\",\"name\":\"uniform india\",\"status\":\"active\",\"score\":73.74,\"quantity\":317,\"active\":true,\"updatedAt\":\"2026-06-06T12:00:00Z\"}]", + "unwrap": true, + "expected": { + "normalized": "[{\"id\":0,\"sku\":\"SKU-481722\",\"name\":\"oscar victor\",\"status\":\"active\",\"score\":71.96,\"quantity\":467,\"active\":false,\"updatedAt\":\"2026-05-06T12:00:00Z\"},{\"id\":1,\"sku\":\"SKU-520316\",\"name\":\"tango charlie\",\"status\":\"failed\",\"score\":56.28,\"quantity\":458,\"active\":false,\"updatedAt\":\"2026-04-10T12:00:00Z\"},{\"id\":2,\"sku\":\"SKU-596239\",\"name\":\"hotel romeo\",\"status\":\"pending\",\"score\":44.98,\"quantity\":421,\"active\":true,\"updatedAt\":\"2026-06-25T12:00:00Z\"},{\"id\":3,\"sku\":\"SKU-431891\",\"name\":\"november hotel\",\"status\":\"active\",\"score\":69.67,\"quantity\":156,\"active\":true,\"updatedAt\":\"2026-06-03T12:00:00Z\"},{\"id\":4,\"sku\":\"SKU-475391\",\"name\":\"tango delta\",\"status\":\"failed\",\"score\":17.84,\"quantity\":354,\"active\":true,\"updatedAt\":\"2026-02-12T12:00:00Z\"},{\"id\":5,\"sku\":\"SKU-163437\",\"name\":\"papa india\",\"status\":\"active\",\"score\":62.89,\"quantity\":443,\"active\":false,\"updatedAt\":\"2026-05-12T12:00:00Z\"},{\"id\":6,\"sku\":\"SKU-131392\",\"name\":\"uniform india\",\"status\":\"active\",\"score\":73.74,\"quantity\":317,\"active\":true,\"updatedAt\":\"2026-06-06T12:00:00Z\"}]", + "encoded": "[7]{id,sku,name,status,score,quantity,active,updatedAt}:\n 0,\"SKU-481722\",oscar victor,active,71.96,467,false,\"2026-05-06T12:00:00Z\"\n 1,\"SKU-520316\",tango charlie,failed,56.28,458,false,\"2026-04-10T12:00:00Z\"\n 2,\"SKU-596239\",hotel romeo,pending,44.98,421,true,\"2026-06-25T12:00:00Z\"\n 3,\"SKU-431891\",november hotel,active,69.67,156,true,\"2026-06-03T12:00:00Z\"\n 4,\"SKU-475391\",tango delta,failed,17.84,354,true,\"2026-02-12T12:00:00Z\"\n 5,\"SKU-163437\",papa india,active,62.89,443,false,\"2026-05-12T12:00:00Z\"\n 6,\"SKU-131392\",uniform india,active,73.74,317,true,\"2026-06-06T12:00:00Z\"" + } + }, + { + "name": "bench-1kb-2", + "rawContent": "{\"meta\":{\"source\":\"bench\",\"version\":3,\"total\":5},\"entries\":{\"entry_0\":{\"id\":0,\"sku\":\"SKU-603932\",\"name\":\"lima echo\",\"status\":\"active\",\"score\":70.56,\"quantity\":354,\"active\":false,\"updatedAt\":\"2026-02-10T12:00:00Z\",\"nested\":{\"tags\":[\"alpha\",\"foxtrot\"],\"depth\":2}},\"entry_1\":{\"id\":1,\"sku\":\"SKU-993520\",\"name\":\"november zulu\",\"status\":\"active\",\"score\":17.08,\"quantity\":280,\"active\":false,\"updatedAt\":\"2026-03-13T12:00:00Z\",\"nested\":{\"tags\":[\"kilo\",\"quebec\"],\"depth\":2}},\"entry_2\":{\"id\":2,\"sku\":\"SKU-398513\",\"name\":\"india victor\",\"status\":\"pending\",\"score\":49.53,\"quantity\":305,\"active\":true,\"updatedAt\":\"2026-04-07T12:00:00Z\",\"nested\":{\"tags\":[\"hotel\",\"hotel\"],\"depth\":2}},\"entry_3\":{\"id\":3,\"sku\":\"SKU-320565\",\"name\":\"quebec foxtrot\",\"status\":\"active\",\"score\":69.5,\"quantity\":157,\"active\":false,\"updatedAt\":\"2026-02-02T12:00:00Z\",\"nested\":{\"tags\":[\"november\",\"echo\"],\"depth\":2}},\"entry_4\":{\"id\":4,\"sku\":\"SKU-481534\",\"name\":\"juliet romeo\",\"status\":\"failed\",\"score\":81.17,\"quantity\":219,\"active\":false,\"updatedAt\":\"2026-06-15T12:00:00Z\",\"nested\":{\"tags\":[\"uniform\",\"victor\"],\"depth\":2}}}}", + "unwrap": true, + "expected": { + "normalized": "{\"meta\":{\"source\":\"bench\",\"version\":3,\"total\":5},\"entries\":{\"entry_0\":{\"id\":0,\"sku\":\"SKU-603932\",\"name\":\"lima echo\",\"status\":\"active\",\"score\":70.56,\"quantity\":354,\"active\":false,\"updatedAt\":\"2026-02-10T12:00:00Z\",\"nested\":{\"tags\":[\"alpha\",\"foxtrot\"],\"depth\":2}},\"entry_1\":{\"id\":1,\"sku\":\"SKU-993520\",\"name\":\"november zulu\",\"status\":\"active\",\"score\":17.08,\"quantity\":280,\"active\":false,\"updatedAt\":\"2026-03-13T12:00:00Z\",\"nested\":{\"tags\":[\"kilo\",\"quebec\"],\"depth\":2}},\"entry_2\":{\"id\":2,\"sku\":\"SKU-398513\",\"name\":\"india victor\",\"status\":\"pending\",\"score\":49.53,\"quantity\":305,\"active\":true,\"updatedAt\":\"2026-04-07T12:00:00Z\",\"nested\":{\"tags\":[\"hotel\",\"hotel\"],\"depth\":2}},\"entry_3\":{\"id\":3,\"sku\":\"SKU-320565\",\"name\":\"quebec foxtrot\",\"status\":\"active\",\"score\":69.5,\"quantity\":157,\"active\":false,\"updatedAt\":\"2026-02-02T12:00:00Z\",\"nested\":{\"tags\":[\"november\",\"echo\"],\"depth\":2}},\"entry_4\":{\"id\":4,\"sku\":\"SKU-481534\",\"name\":\"juliet romeo\",\"status\":\"failed\",\"score\":81.17,\"quantity\":219,\"active\":false,\"updatedAt\":\"2026-06-15T12:00:00Z\",\"nested\":{\"tags\":[\"uniform\",\"victor\"],\"depth\":2}}}}", + "encoded": "meta:\n source: bench\n version: 3\n total: 5\nentries:\n entry_0:\n id: 0\n sku: \"SKU-603932\"\n name: lima echo\n status: active\n score: 70.56\n quantity: 354\n active: false\n updatedAt: \"2026-02-10T12:00:00Z\"\n nested:\n tags[2]: alpha,foxtrot\n depth: 2\n entry_1:\n id: 1\n sku: \"SKU-993520\"\n name: november zulu\n status: active\n score: 17.08\n quantity: 280\n active: false\n updatedAt: \"2026-03-13T12:00:00Z\"\n nested:\n tags[2]: kilo,quebec\n depth: 2\n entry_2:\n id: 2\n sku: \"SKU-398513\"\n name: india victor\n status: pending\n score: 49.53\n quantity: 305\n active: true\n updatedAt: \"2026-04-07T12:00:00Z\"\n nested:\n tags[2]: hotel,hotel\n depth: 2\n entry_3:\n id: 3\n sku: \"SKU-320565\"\n name: quebec foxtrot\n status: active\n score: 69.5\n quantity: 157\n active: false\n updatedAt: \"2026-02-02T12:00:00Z\"\n nested:\n tags[2]: november,echo\n depth: 2\n entry_4:\n id: 4\n sku: \"SKU-481534\"\n name: juliet romeo\n status: failed\n score: 81.17\n quantity: 219\n active: false\n updatedAt: \"2026-06-15T12:00:00Z\"\n nested:\n tags[2]: uniform,victor\n depth: 2" + } + }, + { + "name": "bench-1kb-3", + "rawContent": "[{\"id\":0,\"sku\":\"SKU-450102\",\"name\":\"delta oscar\",\"status\":\"failed\",\"score\":20.84,\"quantity\":194,\"active\":true,\"updatedAt\":\"2026-06-14T12:00:00Z\"},{\"id\":1,\"sku\":\"SKU-859482\",\"name\":\"uniform romeo\",\"status\":\"active\",\"score\":6.93,\"quantity\":84,\"active\":true,\"updatedAt\":\"2026-05-22T12:00:00Z\"},{\"id\":2,\"sku\":\"SKU-991456\",\"name\":\"tango papa\",\"status\":\"failed\",\"score\":85.3,\"quantity\":299,\"active\":false,\"updatedAt\":\"2026-03-25T12:00:00Z\"},{\"id\":3,\"sku\":\"SKU-680988\",\"name\":\"india whiskey\",\"status\":\"archived\",\"score\":32.93,\"quantity\":422,\"active\":false,\"updatedAt\":\"2026-03-01T12:00:00Z\"},{\"id\":4,\"sku\":\"SKU-85502\",\"name\":\"papa romeo\",\"status\":\"failed\",\"score\":87.95,\"quantity\":233,\"active\":true,\"updatedAt\":\"2026-01-08T12:00:00Z\"},{\"id\":5,\"sku\":\"SKU-139138\",\"name\":\"bravo golf\",\"status\":\"pending\",\"score\":56.88,\"quantity\":358,\"active\":true,\"updatedAt\":\"2026-05-06T12:00:00Z\"},{\"id\":6,\"sku\":\"SKU-666356\",\"name\":\"mike kilo\",\"status\":\"pending\",\"score\":45.91,\"quantity\":212,\"active\":false,\"updatedAt\":\"2026-02-05T12:00:00Z\"},{\"id\":7,\"sku\":\"SKU-504597\",\"name\":\"golf mike\",\"status\":\"archived\",\"score\":64.74,\"quantity\":218,\"active\":false,\"updatedAt\":\"2026-01-05T12:00:00Z\"}]", + "unwrap": false, + "expected": { + "normalized": "[{\"id\":0,\"sku\":\"SKU-450102\",\"name\":\"delta oscar\",\"status\":\"failed\",\"score\":20.84,\"quantity\":194,\"active\":true,\"updatedAt\":\"2026-06-14T12:00:00Z\"},{\"id\":1,\"sku\":\"SKU-859482\",\"name\":\"uniform romeo\",\"status\":\"active\",\"score\":6.93,\"quantity\":84,\"active\":true,\"updatedAt\":\"2026-05-22T12:00:00Z\"},{\"id\":2,\"sku\":\"SKU-991456\",\"name\":\"tango papa\",\"status\":\"failed\",\"score\":85.3,\"quantity\":299,\"active\":false,\"updatedAt\":\"2026-03-25T12:00:00Z\"},{\"id\":3,\"sku\":\"SKU-680988\",\"name\":\"india whiskey\",\"status\":\"archived\",\"score\":32.93,\"quantity\":422,\"active\":false,\"updatedAt\":\"2026-03-01T12:00:00Z\"},{\"id\":4,\"sku\":\"SKU-85502\",\"name\":\"papa romeo\",\"status\":\"failed\",\"score\":87.95,\"quantity\":233,\"active\":true,\"updatedAt\":\"2026-01-08T12:00:00Z\"},{\"id\":5,\"sku\":\"SKU-139138\",\"name\":\"bravo golf\",\"status\":\"pending\",\"score\":56.88,\"quantity\":358,\"active\":true,\"updatedAt\":\"2026-05-06T12:00:00Z\"},{\"id\":6,\"sku\":\"SKU-666356\",\"name\":\"mike kilo\",\"status\":\"pending\",\"score\":45.91,\"quantity\":212,\"active\":false,\"updatedAt\":\"2026-02-05T12:00:00Z\"},{\"id\":7,\"sku\":\"SKU-504597\",\"name\":\"golf mike\",\"status\":\"archived\",\"score\":64.74,\"quantity\":218,\"active\":false,\"updatedAt\":\"2026-01-05T12:00:00Z\"}]", + "encoded": "[8]{id,sku,name,status,score,quantity,active,updatedAt}:\n 0,\"SKU-450102\",delta oscar,failed,20.84,194,true,\"2026-06-14T12:00:00Z\"\n 1,\"SKU-859482\",uniform romeo,active,6.93,84,true,\"2026-05-22T12:00:00Z\"\n 2,\"SKU-991456\",tango papa,failed,85.3,299,false,\"2026-03-25T12:00:00Z\"\n 3,\"SKU-680988\",india whiskey,archived,32.93,422,false,\"2026-03-01T12:00:00Z\"\n 4,\"SKU-85502\",papa romeo,failed,87.95,233,true,\"2026-01-08T12:00:00Z\"\n 5,\"SKU-139138\",bravo golf,pending,56.88,358,true,\"2026-05-06T12:00:00Z\"\n 6,\"SKU-666356\",mike kilo,pending,45.91,212,false,\"2026-02-05T12:00:00Z\"\n 7,\"SKU-504597\",golf mike,archived,64.74,218,false,\"2026-01-05T12:00:00Z\"" + } + }, + { + "name": "bench-1kb-4", + "rawContent": "Tool run 943 output: bravo uniform kilo mike charlie tango bravo india foxtrot quebec november charlie zulu sierra papa tango november tango mike echo bravo charlie bravo golf quebec quebec november zulu alpha juliet india golf tango mike echo whiskey whiskey golf mike uniform whiskey quebec november tango hotel lima sierra november quebec november november zulu zulu sierra hotel papa quebec uniform juliet victor hotel victor juliet india mike uniform oscar uniform quebec delta golf zulu lima golf papa sierra romeo romeo echo uniform victor november lima bravo lima victor quebec kilo kilo oscar romeo mike bravo echo charlie charlie juliet tango uniform quebec charlie alpha golf quebec golf uniform papa victor victor india charlie papa foxtrot lima golf golf romeo mike lima oscar charlie sierra quebec whiskey charlie bravo alpha india foxtrot foxtrot zulu november uniform india papa alpha romeo tango juliet echo sierra mike delta bravo sierra kilo uniform tango lima whiskey echo delta november quebec echo sierra", + "unwrap": true, + "expected": { + "normalized": "Tool run 943 output: bravo uniform kilo mike charlie tango bravo india foxtrot quebec november charlie zulu sierra papa tango november tango mike echo bravo charlie bravo golf quebec quebec november zulu alpha juliet india golf tango mike echo whiskey whiskey golf mike uniform whiskey quebec november tango hotel lima sierra november quebec november november zulu zulu sierra hotel papa quebec uniform juliet victor hotel victor juliet india mike uniform oscar uniform quebec delta golf zulu lima golf papa sierra romeo romeo echo uniform victor november lima bravo lima victor quebec kilo kilo oscar romeo mike bravo echo charlie charlie juliet tango uniform quebec charlie alpha golf quebec golf uniform papa victor victor india charlie papa foxtrot lima golf golf romeo mike lima oscar charlie sierra quebec whiskey charlie bravo alpha india foxtrot foxtrot zulu november uniform india papa alpha romeo tango juliet echo sierra mike delta bravo sierra kilo uniform tango lima whiskey echo delta november quebec echo sierra", + "encoded": null + } + }, + { + "name": "bench-1kb-5", + "rawContent": "[{\"id\":0,\"sku\":\"SKU-241297\",\"name\":\"delta papa\",\"status\":\"archived\",\"score\":55.54,\"quantity\":102,\"active\":true,\"updatedAt\":\"2026-04-21T12:00:00Z\"},{\"id\":1,\"sku\":\"SKU-49849\",\"name\":\"whiskey november\",\"status\":\"pending\",\"score\":4.71,\"quantity\":163,\"active\":true,\"updatedAt\":\"2026-05-08T12:00:00Z\"},{\"id\":2,\"sku\":\"SKU-862775\",\"name\":\"kilo echo\",\"status\":\"archived\",\"score\":50.75,\"quantity\":94,\"active\":true,\"updatedAt\":\"2026-01-04T12:00:00Z\"},{\"id\":3,\"sku\":\"SKU-994988\",\"name\":\"foxtrot lima\",\"status\":\"archived\",\"score\":61.96,\"quantity\":242,\"active\":true,\"updatedAt\":\"2026-01-28T12:00:00Z\"},{\"id\":4,\"sku\":\"SKU-829234\",\"name\":\"india echo\",\"status\":\"archived\",\"score\":76.22,\"quantity\":222,\"active\":false,\"updatedAt\":\"2026-02-14T12:00:00Z\"},{\"id\":5,\"sku\":\"SKU-34817\",\"name\":\"uniform india\",\"status\":\"pending\",\"score\":25.15,\"quantity\":251,\"active\":false,\"updatedAt\":\"2026-04-02T12:00:00Z\"},{\"id\":6,\"sku\":\"SKU-901145\",\"name\":\"sierra papa\",\"status\":\"failed\",\"score\":5.38,\"quantity\":293,\"active\":true,\"updatedAt\":\"2026-03-15T12:00:00Z\"}]", + "unwrap": true, + "expected": { + "normalized": "[{\"id\":0,\"sku\":\"SKU-241297\",\"name\":\"delta papa\",\"status\":\"archived\",\"score\":55.54,\"quantity\":102,\"active\":true,\"updatedAt\":\"2026-04-21T12:00:00Z\"},{\"id\":1,\"sku\":\"SKU-49849\",\"name\":\"whiskey november\",\"status\":\"pending\",\"score\":4.71,\"quantity\":163,\"active\":true,\"updatedAt\":\"2026-05-08T12:00:00Z\"},{\"id\":2,\"sku\":\"SKU-862775\",\"name\":\"kilo echo\",\"status\":\"archived\",\"score\":50.75,\"quantity\":94,\"active\":true,\"updatedAt\":\"2026-01-04T12:00:00Z\"},{\"id\":3,\"sku\":\"SKU-994988\",\"name\":\"foxtrot lima\",\"status\":\"archived\",\"score\":61.96,\"quantity\":242,\"active\":true,\"updatedAt\":\"2026-01-28T12:00:00Z\"},{\"id\":4,\"sku\":\"SKU-829234\",\"name\":\"india echo\",\"status\":\"archived\",\"score\":76.22,\"quantity\":222,\"active\":false,\"updatedAt\":\"2026-02-14T12:00:00Z\"},{\"id\":5,\"sku\":\"SKU-34817\",\"name\":\"uniform india\",\"status\":\"pending\",\"score\":25.15,\"quantity\":251,\"active\":false,\"updatedAt\":\"2026-04-02T12:00:00Z\"},{\"id\":6,\"sku\":\"SKU-901145\",\"name\":\"sierra papa\",\"status\":\"failed\",\"score\":5.38,\"quantity\":293,\"active\":true,\"updatedAt\":\"2026-03-15T12:00:00Z\"}]", + "encoded": "[7]{id,sku,name,status,score,quantity,active,updatedAt}:\n 0,\"SKU-241297\",delta papa,archived,55.54,102,true,\"2026-04-21T12:00:00Z\"\n 1,\"SKU-49849\",whiskey november,pending,4.71,163,true,\"2026-05-08T12:00:00Z\"\n 2,\"SKU-862775\",kilo echo,archived,50.75,94,true,\"2026-01-04T12:00:00Z\"\n 3,\"SKU-994988\",foxtrot lima,archived,61.96,242,true,\"2026-01-28T12:00:00Z\"\n 4,\"SKU-829234\",india echo,archived,76.22,222,false,\"2026-02-14T12:00:00Z\"\n 5,\"SKU-34817\",uniform india,pending,25.15,251,false,\"2026-04-02T12:00:00Z\"\n 6,\"SKU-901145\",sierra papa,failed,5.38,293,true,\"2026-03-15T12:00:00Z\"" + } + }, + { + "name": "bench-1kb-6", + "rawContent": "[{\"type\":\"text\",\"text\":\"[{\\\"id\\\":0,\\\"sku\\\":\\\"SKU-475386\\\",\\\"name\\\":\\\"kilo november\\\",\\\"status\\\":\\\"active\\\",\\\"score\\\":70.83,\\\"quantity\\\":1,\\\"active\\\":true,\\\"updatedAt\\\":\\\"2026-02-11T12:00:00Z\\\"},{\\\"id\\\":1,\\\"sku\\\":\\\"SKU-725772\\\",\\\"name\\\":\\\"whiskey golf\\\",\\\"status\\\":\\\"archived\\\",\\\"score\\\":90.26,\\\"quantity\\\":231,\\\"active\\\":false,\\\"updatedAt\\\":\\\"2026-04-14T12:00:00Z\\\"},{\\\"id\\\":2,\\\"sku\\\":\\\"SKU-442017\\\",\\\"name\\\":\\\"lima sierra\\\",\\\"status\\\":\\\"active\\\",\\\"score\\\":71.48,\\\"quantity\\\":297,\\\"active\\\":true,\\\"updatedAt\\\":\\\"2026-05-14T12:00:00Z\\\"},{\\\"id\\\":3,\\\"sku\\\":\\\"SKU-8258\\\",\\\"name\\\":\\\"victor bravo\\\",\\\"status\\\":\\\"archived\\\",\\\"score\\\":48.68,\\\"quantity\\\":340,\\\"active\\\":true,\\\"updatedAt\\\":\\\"2026-04-24T12:00:00Z\\\"},{\\\"id\\\":4,\\\"sku\\\":\\\"SKU-664289\\\",\\\"name\\\":\\\"zulu zulu\\\",\\\"status\\\":\\\"failed\\\",\\\"score\\\":81.17,\\\"quantity\\\":478,\\\"active\\\":true,\\\"updatedAt\\\":\\\"2026-01-03T12:00:00Z\\\"},{\\\"id\\\":5,\\\"sku\\\":\\\"SKU-992953\\\",\\\"name\\\":\\\"alpha lima\\\",\\\"status\\\":\\\"failed\\\",\\\"score\\\":67.92,\\\"quantity\\\":388,\\\"active\\\":false,\\\"updatedAt\\\":\\\"2026-06-13T12:00:00Z\\\"},{\\\"id\\\":6,\\\"sku\\\":\\\"SKU-923099\\\",\\\"name\\\":\\\"papa golf\\\",\\\"status\\\":\\\"active\\\",\\\"score\\\":37.45,\\\"quantity\\\":362,\\\"active\\\":false,\\\"updatedAt\\\":\\\"2026-01-07T12:00:00Z\\\"},{\\\"id\\\":7,\\\"sku\\\":\\\"SKU-620884\\\",\\\"name\\\":\\\"india papa\\\",\\\"status\\\":\\\"failed\\\",\\\"score\\\":40.95,\\\"quantity\\\":396,\\\"active\\\":true,\\\"updatedAt\\\":\\\"2026-01-20T12:00:00Z\\\"}]\"}]", + "unwrap": true, + "expected": { + "normalized": "[{\"id\":0,\"sku\":\"SKU-475386\",\"name\":\"kilo november\",\"status\":\"active\",\"score\":70.83,\"quantity\":1,\"active\":true,\"updatedAt\":\"2026-02-11T12:00:00Z\"},{\"id\":1,\"sku\":\"SKU-725772\",\"name\":\"whiskey golf\",\"status\":\"archived\",\"score\":90.26,\"quantity\":231,\"active\":false,\"updatedAt\":\"2026-04-14T12:00:00Z\"},{\"id\":2,\"sku\":\"SKU-442017\",\"name\":\"lima sierra\",\"status\":\"active\",\"score\":71.48,\"quantity\":297,\"active\":true,\"updatedAt\":\"2026-05-14T12:00:00Z\"},{\"id\":3,\"sku\":\"SKU-8258\",\"name\":\"victor bravo\",\"status\":\"archived\",\"score\":48.68,\"quantity\":340,\"active\":true,\"updatedAt\":\"2026-04-24T12:00:00Z\"},{\"id\":4,\"sku\":\"SKU-664289\",\"name\":\"zulu zulu\",\"status\":\"failed\",\"score\":81.17,\"quantity\":478,\"active\":true,\"updatedAt\":\"2026-01-03T12:00:00Z\"},{\"id\":5,\"sku\":\"SKU-992953\",\"name\":\"alpha lima\",\"status\":\"failed\",\"score\":67.92,\"quantity\":388,\"active\":false,\"updatedAt\":\"2026-06-13T12:00:00Z\"},{\"id\":6,\"sku\":\"SKU-923099\",\"name\":\"papa golf\",\"status\":\"active\",\"score\":37.45,\"quantity\":362,\"active\":false,\"updatedAt\":\"2026-01-07T12:00:00Z\"},{\"id\":7,\"sku\":\"SKU-620884\",\"name\":\"india papa\",\"status\":\"failed\",\"score\":40.95,\"quantity\":396,\"active\":true,\"updatedAt\":\"2026-01-20T12:00:00Z\"}]", + "encoded": "[8]{id,sku,name,status,score,quantity,active,updatedAt}:\n 0,\"SKU-475386\",kilo november,active,70.83,1,true,\"2026-02-11T12:00:00Z\"\n 1,\"SKU-725772\",whiskey golf,archived,90.26,231,false,\"2026-04-14T12:00:00Z\"\n 2,\"SKU-442017\",lima sierra,active,71.48,297,true,\"2026-05-14T12:00:00Z\"\n 3,\"SKU-8258\",victor bravo,archived,48.68,340,true,\"2026-04-24T12:00:00Z\"\n 4,\"SKU-664289\",zulu zulu,failed,81.17,478,true,\"2026-01-03T12:00:00Z\"\n 5,\"SKU-992953\",alpha lima,failed,67.92,388,false,\"2026-06-13T12:00:00Z\"\n 6,\"SKU-923099\",papa golf,active,37.45,362,false,\"2026-01-07T12:00:00Z\"\n 7,\"SKU-620884\",india papa,failed,40.95,396,true,\"2026-01-20T12:00:00Z\"" + } + }, + { + "name": "bench-1kb-7", + "rawContent": "[{\"type\":\"text\",\"text\":\"{\\\"meta\\\":{\\\"source\\\":\\\"bench\\\",\\\"version\\\":3,\\\"total\\\":5},\\\"entries\\\":{\\\"entry_0\\\":{\\\"id\\\":0,\\\"sku\\\":\\\"SKU-875281\\\",\\\"name\\\":\\\"golf oscar\\\",\\\"status\\\":\\\"failed\\\",\\\"score\\\":46.69,\\\"quantity\\\":190,\\\"active\\\":true,\\\"updatedAt\\\":\\\"2026-04-09T12:00:00Z\\\",\\\"nested\\\":{\\\"tags\\\":[\\\"lima\\\",\\\"mike\\\"],\\\"depth\\\":2}},\\\"entry_1\\\":{\\\"id\\\":1,\\\"sku\\\":\\\"SKU-995847\\\",\\\"name\\\":\\\"echo oscar\\\",\\\"status\\\":\\\"active\\\",\\\"score\\\":64.41,\\\"quantity\\\":397,\\\"active\\\":false,\\\"updatedAt\\\":\\\"2026-04-24T12:00:00Z\\\",\\\"nested\\\":{\\\"tags\\\":[\\\"juliet\\\",\\\"oscar\\\"],\\\"depth\\\":2}},\\\"entry_2\\\":{\\\"id\\\":2,\\\"sku\\\":\\\"SKU-169022\\\",\\\"name\\\":\\\"zulu echo\\\",\\\"status\\\":\\\"archived\\\",\\\"score\\\":70.82,\\\"quantity\\\":283,\\\"active\\\":true,\\\"updatedAt\\\":\\\"2026-04-26T12:00:00Z\\\",\\\"nested\\\":{\\\"tags\\\":[\\\"sierra\\\",\\\"alpha\\\"],\\\"depth\\\":2}},\\\"entry_3\\\":{\\\"id\\\":3,\\\"sku\\\":\\\"SKU-60400\\\",\\\"name\\\":\\\"charlie victor\\\",\\\"status\\\":\\\"active\\\",\\\"score\\\":3.47,\\\"quantity\\\":261,\\\"active\\\":false,\\\"updatedAt\\\":\\\"2026-05-18T12:00:00Z\\\",\\\"nested\\\":{\\\"tags\\\":[\\\"alpha\\\",\\\"juliet\\\"],\\\"depth\\\":2}},\\\"entry_4\\\":{\\\"id\\\":4,\\\"sku\\\":\\\"SKU-825996\\\",\\\"name\\\":\\\"india echo\\\",\\\"status\\\":\\\"archived\\\",\\\"score\\\":65.27,\\\"quantity\\\":24,\\\"active\\\":true,\\\"updatedAt\\\":\\\"2026-04-24T12:00:00Z\\\",\\\"nested\\\":{\\\"tags\\\":[\\\"november\\\",\\\"juliet\\\"],\\\"depth\\\":2}}}}\"}]", + "unwrap": true, + "expected": { + "normalized": "{\"meta\":{\"source\":\"bench\",\"version\":3,\"total\":5},\"entries\":{\"entry_0\":{\"id\":0,\"sku\":\"SKU-875281\",\"name\":\"golf oscar\",\"status\":\"failed\",\"score\":46.69,\"quantity\":190,\"active\":true,\"updatedAt\":\"2026-04-09T12:00:00Z\",\"nested\":{\"tags\":[\"lima\",\"mike\"],\"depth\":2}},\"entry_1\":{\"id\":1,\"sku\":\"SKU-995847\",\"name\":\"echo oscar\",\"status\":\"active\",\"score\":64.41,\"quantity\":397,\"active\":false,\"updatedAt\":\"2026-04-24T12:00:00Z\",\"nested\":{\"tags\":[\"juliet\",\"oscar\"],\"depth\":2}},\"entry_2\":{\"id\":2,\"sku\":\"SKU-169022\",\"name\":\"zulu echo\",\"status\":\"archived\",\"score\":70.82,\"quantity\":283,\"active\":true,\"updatedAt\":\"2026-04-26T12:00:00Z\",\"nested\":{\"tags\":[\"sierra\",\"alpha\"],\"depth\":2}},\"entry_3\":{\"id\":3,\"sku\":\"SKU-60400\",\"name\":\"charlie victor\",\"status\":\"active\",\"score\":3.47,\"quantity\":261,\"active\":false,\"updatedAt\":\"2026-05-18T12:00:00Z\",\"nested\":{\"tags\":[\"alpha\",\"juliet\"],\"depth\":2}},\"entry_4\":{\"id\":4,\"sku\":\"SKU-825996\",\"name\":\"india echo\",\"status\":\"archived\",\"score\":65.27,\"quantity\":24,\"active\":true,\"updatedAt\":\"2026-04-24T12:00:00Z\",\"nested\":{\"tags\":[\"november\",\"juliet\"],\"depth\":2}}}}", + "encoded": "meta:\n source: bench\n version: 3\n total: 5\nentries:\n entry_0:\n id: 0\n sku: \"SKU-875281\"\n name: golf oscar\n status: failed\n score: 46.69\n quantity: 190\n active: true\n updatedAt: \"2026-04-09T12:00:00Z\"\n nested:\n tags[2]: lima,mike\n depth: 2\n entry_1:\n id: 1\n sku: \"SKU-995847\"\n name: echo oscar\n status: active\n score: 64.41\n quantity: 397\n active: false\n updatedAt: \"2026-04-24T12:00:00Z\"\n nested:\n tags[2]: juliet,oscar\n depth: 2\n entry_2:\n id: 2\n sku: \"SKU-169022\"\n name: zulu echo\n status: archived\n score: 70.82\n quantity: 283\n active: true\n updatedAt: \"2026-04-26T12:00:00Z\"\n nested:\n tags[2]: sierra,alpha\n depth: 2\n entry_3:\n id: 3\n sku: \"SKU-60400\"\n name: charlie victor\n status: active\n score: 3.47\n quantity: 261\n active: false\n updatedAt: \"2026-05-18T12:00:00Z\"\n nested:\n tags[2]: alpha,juliet\n depth: 2\n entry_4:\n id: 4\n sku: \"SKU-825996\"\n name: india echo\n status: archived\n score: 65.27\n quantity: 24\n active: true\n updatedAt: \"2026-04-24T12:00:00Z\"\n nested:\n tags[2]: november,juliet\n depth: 2" + } + }, + { + "name": "bench-1kb-8", + "rawContent": "[{\"id\":0,\"sku\":\"SKU-588157\",\"name\":\"juliet oscar\",\"status\":\"active\",\"score\":13.52,\"quantity\":353,\"active\":true,\"updatedAt\":\"2026-02-26T12:00:00Z\"},{\"id\":1,\"sku\":\"SKU-943313\",\"name\":\"kilo oscar\",\"status\":\"pending\",\"score\":66.62,\"quantity\":489,\"active\":true,\"updatedAt\":\"2026-05-10T12:00:00Z\"},{\"id\":2,\"sku\":\"SKU-909215\",\"name\":\"mike whiskey\",\"status\":\"active\",\"score\":15.08,\"quantity\":159,\"active\":true,\"updatedAt\":\"2026-04-28T12:00:00Z\"},{\"id\":3,\"sku\":\"SKU-21248\",\"name\":\"papa oscar\",\"status\":\"failed\",\"score\":25.06,\"quantity\":481,\"active\":false,\"updatedAt\":\"2026-06-09T12:00:00Z\"},{\"id\":4,\"sku\":\"SKU-512470\",\"name\":\"juliet foxtrot\",\"status\":\"pending\",\"score\":94.43,\"quantity\":313,\"active\":false,\"updatedAt\":\"2026-04-11T12:00:00Z\"},{\"id\":5,\"sku\":\"SKU-445960\",\"name\":\"echo november\",\"status\":\"failed\",\"score\":65.91,\"quantity\":270,\"active\":true,\"updatedAt\":\"2026-04-24T12:00:00Z\"},{\"id\":6,\"sku\":\"SKU-443750\",\"name\":\"papa kilo\",\"status\":\"archived\",\"score\":66.03,\"quantity\":312,\"active\":false,\"updatedAt\":\"2026-02-01T12:00:00Z\"}]", + "unwrap": true, + "expected": { + "normalized": "[{\"id\":0,\"sku\":\"SKU-588157\",\"name\":\"juliet oscar\",\"status\":\"active\",\"score\":13.52,\"quantity\":353,\"active\":true,\"updatedAt\":\"2026-02-26T12:00:00Z\"},{\"id\":1,\"sku\":\"SKU-943313\",\"name\":\"kilo oscar\",\"status\":\"pending\",\"score\":66.62,\"quantity\":489,\"active\":true,\"updatedAt\":\"2026-05-10T12:00:00Z\"},{\"id\":2,\"sku\":\"SKU-909215\",\"name\":\"mike whiskey\",\"status\":\"active\",\"score\":15.08,\"quantity\":159,\"active\":true,\"updatedAt\":\"2026-04-28T12:00:00Z\"},{\"id\":3,\"sku\":\"SKU-21248\",\"name\":\"papa oscar\",\"status\":\"failed\",\"score\":25.06,\"quantity\":481,\"active\":false,\"updatedAt\":\"2026-06-09T12:00:00Z\"},{\"id\":4,\"sku\":\"SKU-512470\",\"name\":\"juliet foxtrot\",\"status\":\"pending\",\"score\":94.43,\"quantity\":313,\"active\":false,\"updatedAt\":\"2026-04-11T12:00:00Z\"},{\"id\":5,\"sku\":\"SKU-445960\",\"name\":\"echo november\",\"status\":\"failed\",\"score\":65.91,\"quantity\":270,\"active\":true,\"updatedAt\":\"2026-04-24T12:00:00Z\"},{\"id\":6,\"sku\":\"SKU-443750\",\"name\":\"papa kilo\",\"status\":\"archived\",\"score\":66.03,\"quantity\":312,\"active\":false,\"updatedAt\":\"2026-02-01T12:00:00Z\"}]", + "encoded": "[7]{id,sku,name,status,score,quantity,active,updatedAt}:\n 0,\"SKU-588157\",juliet oscar,active,13.52,353,true,\"2026-02-26T12:00:00Z\"\n 1,\"SKU-943313\",kilo oscar,pending,66.62,489,true,\"2026-05-10T12:00:00Z\"\n 2,\"SKU-909215\",mike whiskey,active,15.08,159,true,\"2026-04-28T12:00:00Z\"\n 3,\"SKU-21248\",papa oscar,failed,25.06,481,false,\"2026-06-09T12:00:00Z\"\n 4,\"SKU-512470\",juliet foxtrot,pending,94.43,313,false,\"2026-04-11T12:00:00Z\"\n 5,\"SKU-445960\",echo november,failed,65.91,270,true,\"2026-04-24T12:00:00Z\"\n 6,\"SKU-443750\",papa kilo,archived,66.03,312,false,\"2026-02-01T12:00:00Z\"" + } + }, + { + "name": "bench-1kb-9", + "rawContent": "Tool run 710 output: juliet hotel alpha delta papa golf bravo lima quebec delta romeo foxtrot golf oscar india india oscar lima tango charlie echo india victor uniform whiskey lima echo papa oscar sierra whiskey juliet juliet delta victor november echo alpha uniform charlie mike mike oscar zulu mike quebec mike victor victor charlie foxtrot whiskey juliet quebec mike kilo hotel india india papa uniform victor foxtrot oscar lima mike romeo november juliet zulu foxtrot kilo bravo delta bravo charlie india sierra mike kilo juliet zulu romeo kilo india romeo golf oscar golf charlie uniform juliet charlie whiskey bravo sierra november bravo oscar victor echo uniform foxtrot zulu kilo kilo alpha juliet bravo juliet lima juliet alpha zulu sierra foxtrot charlie echo quebec tango zulu uniform victor foxtrot quebec mike tango foxtrot alpha uniform victor whiskey foxtrot delta alpha whiskey quebec papa charlie papa quebec tango alpha uniform foxtrot mike india bravo golf victor zulu november echo alpha tango alpha whiskey", + "unwrap": true, + "expected": { + "normalized": "Tool run 710 output: juliet hotel alpha delta papa golf bravo lima quebec delta romeo foxtrot golf oscar india india oscar lima tango charlie echo india victor uniform whiskey lima echo papa oscar sierra whiskey juliet juliet delta victor november echo alpha uniform charlie mike mike oscar zulu mike quebec mike victor victor charlie foxtrot whiskey juliet quebec mike kilo hotel india india papa uniform victor foxtrot oscar lima mike romeo november juliet zulu foxtrot kilo bravo delta bravo charlie india sierra mike kilo juliet zulu romeo kilo india romeo golf oscar golf charlie uniform juliet charlie whiskey bravo sierra november bravo oscar victor echo uniform foxtrot zulu kilo kilo alpha juliet bravo juliet lima juliet alpha zulu sierra foxtrot charlie echo quebec tango zulu uniform victor foxtrot quebec mike tango foxtrot alpha uniform victor whiskey foxtrot delta alpha whiskey quebec papa charlie papa quebec tango alpha uniform foxtrot mike india bravo golf victor zulu november echo alpha tango alpha whiskey", + "encoded": null + } + }, + { + "name": "bench-10kb-0", + "rawContent": "[{\"id\":0,\"sku\":\"SKU-11704\",\"name\":\"bravo zulu\",\"status\":\"archived\",\"score\":52.14,\"quantity\":202,\"active\":false,\"updatedAt\":\"2026-02-16T12:00:00Z\"},{\"id\":1,\"sku\":\"SKU-729822\",\"name\":\"golf delta\",\"status\":\"failed\",\"score\":51.84,\"quantity\":98,\"active\":false,\"updatedAt\":\"2026-02-15T12:00:00Z\"},{\"id\":2,\"sku\":\"SKU-296330\",\"name\":\"zulu foxtrot\",\"status\":\"failed\",\"score\":19.08,\"quantity\":71,\"active\":false,\"updatedAt\":\"2026-02-16T12:00:00Z\"},{\"id\":3,\"sku\":\"SKU-761873\",\"name\":\"bravo victor\",\"status\":\"archived\",\"score\":62.16,\"quantity\":340,\"active\":true,\"updatedAt\":\"2026-04-26T12:00:00Z\"},{\"id\":4,\"sku\":\"SKU-827628\",\"name\":\"alpha echo\",\"status\":\"pending\",\"score\":42.53,\"quantity\":10,\"active\":false,\"updatedAt\":\"2026-05-11T12:00:00Z\"},{\"id\":5,\"sku\":\"SKU-426715\",\"name\":\"lima whiskey\",\"status\":\"archived\",\"score\":68.83,\"quantity\":165,\"active\":false,\"updatedAt\":\"2026-01-04T12:00:00Z\"},{\"id\":6,\"sku\":\"SKU-913034\",\"name\":\"hotel echo\",\"status\":\"archived\",\"score\":33.68,\"quantity\":21,\"active\":true,\"updatedAt\":\"2026-06-16T12:00:00Z\"},{\"id\":7,\"sku\":\"SKU-826577\",\"name\":\"papa whiskey\",\"status\":\"active\",\"score\":52.99,\"quantity\":324,\"active\":true,\"updatedAt\":\"2026-06-24T12:00:00Z\"},{\"id\":8,\"sku\":\"SKU-594234\",\"name\":\"zulu zulu\",\"status\":\"archived\",\"score\":81.85,\"quantity\":2,\"active\":true,\"updatedAt\":\"2026-02-21T12:00:00Z\"},{\"id\":9,\"sku\":\"SKU-549495\",\"name\":\"sierra zulu\",\"status\":\"archived\",\"score\":82.32,\"quantity\":214,\"active\":true,\"updatedAt\":\"2026-05-16T12:00:00Z\"},{\"id\":10,\"sku\":\"SKU-312383\",\"name\":\"foxtrot alpha\",\"status\":\"active\",\"score\":94.34,\"quantity\":416,\"active\":true,\"updatedAt\":\"2026-01-18T12:00:00Z\"},{\"id\":11,\"sku\":\"SKU-594896\",\"name\":\"romeo zulu\",\"status\":\"pending\",\"score\":98.63,\"quantity\":324,\"active\":true,\"updatedAt\":\"2026-04-24T12:00:00Z\"},{\"id\":12,\"sku\":\"SKU-139064\",\"name\":\"zulu mike\",\"status\":\"archived\",\"score\":16.09,\"quantity\":198,\"active\":false,\"updatedAt\":\"2026-05-25T12:00:00Z\"},{\"id\":13,\"sku\":\"SKU-735919\",\"name\":\"papa zulu\",\"status\":\"archived\",\"score\":73.65,\"quantity\":477,\"active\":false,\"updatedAt\":\"2026-02-17T12:00:00Z\"},{\"id\":14,\"sku\":\"SKU-795681\",\"name\":\"uniform echo\",\"status\":\"archived\",\"score\":47.77,\"quantity\":262,\"active\":false,\"updatedAt\":\"2026-02-13T12:00:00Z\"},{\"id\":15,\"sku\":\"SKU-477512\",\"name\":\"echo alpha\",\"status\":\"active\",\"score\":2.41,\"quantity\":53,\"active\":true,\"updatedAt\":\"2026-06-02T12:00:00Z\"},{\"id\":16,\"sku\":\"SKU-174454\",\"name\":\"juliet lima\",\"status\":\"archived\",\"score\":36.82,\"quantity\":180,\"active\":true,\"updatedAt\":\"2026-05-23T12:00:00Z\"},{\"id\":17,\"sku\":\"SKU-529989\",\"name\":\"tango juliet\",\"status\":\"archived\",\"score\":29.26,\"quantity\":263,\"active\":true,\"updatedAt\":\"2026-01-08T12:00:00Z\"},{\"id\":18,\"sku\":\"SKU-826842\",\"name\":\"tango sierra\",\"status\":\"pending\",\"score\":35.69,\"quantity\":460,\"active\":false,\"updatedAt\":\"2026-03-14T12:00:00Z\"},{\"id\":19,\"sku\":\"SKU-830306\",\"name\":\"juliet zulu\",\"status\":\"archived\",\"score\":43.63,\"quantity\":169,\"active\":true,\"updatedAt\":\"2026-02-25T12:00:00Z\"},{\"id\":20,\"sku\":\"SKU-572258\",\"name\":\"zulu uniform\",\"status\":\"pending\",\"score\":43.68,\"quantity\":348,\"active\":true,\"updatedAt\":\"2026-04-19T12:00:00Z\"},{\"id\":21,\"sku\":\"SKU-162384\",\"name\":\"quebec juliet\",\"status\":\"archived\",\"score\":85.6,\"quantity\":191,\"active\":false,\"updatedAt\":\"2026-01-20T12:00:00Z\"},{\"id\":22,\"sku\":\"SKU-732128\",\"name\":\"zulu hotel\",\"status\":\"pending\",\"score\":6.74,\"quantity\":360,\"active\":true,\"updatedAt\":\"2026-05-24T12:00:00Z\"},{\"id\":23,\"sku\":\"SKU-962738\",\"name\":\"foxtrot zulu\",\"status\":\"pending\",\"score\":53.84,\"quantity\":195,\"active\":true,\"updatedAt\":\"2026-05-18T12:00:00Z\"},{\"id\":24,\"sku\":\"SKU-725523\",\"name\":\"oscar oscar\",\"status\":\"pending\",\"score\":83.19,\"quantity\":241,\"active\":false,\"updatedAt\":\"2026-05-13T12:00:00Z\"},{\"id\":25,\"sku\":\"SKU-559534\",\"name\":\"november victor\",\"status\":\"failed\",\"score\":15.33,\"quantity\":50,\"active\":false,\"updatedAt\":\"2026-04-11T12:00:00Z\"},{\"id\":26,\"sku\":\"SKU-910057\",\"name\":\"uniform mike\",\"status\":\"failed\",\"score\":27.87,\"quantity\":246,\"active\":true,\"updatedAt\":\"2026-05-23T12:00:00Z\"},{\"id\":27,\"sku\":\"SKU-333534\",\"name\":\"victor zulu\",\"status\":\"failed\",\"score\":0.27,\"quantity\":83,\"active\":true,\"updatedAt\":\"2026-01-03T12:00:00Z\"},{\"id\":28,\"sku\":\"SKU-911823\",\"name\":\"golf oscar\",\"status\":\"pending\",\"score\":79.39,\"quantity\":359,\"active\":false,\"updatedAt\":\"2026-03-28T12:00:00Z\"},{\"id\":29,\"sku\":\"SKU-82374\",\"name\":\"charlie juliet\",\"status\":\"failed\",\"score\":69.13,\"quantity\":135,\"active\":false,\"updatedAt\":\"2026-01-14T12:00:00Z\"},{\"id\":30,\"sku\":\"SKU-437150\",\"name\":\"november echo\",\"status\":\"failed\",\"score\":30.32,\"quantity\":345,\"active\":true,\"updatedAt\":\"2026-01-06T12:00:00Z\"},{\"id\":31,\"sku\":\"SKU-717183\",\"name\":\"victor golf\",\"status\":\"failed\",\"score\":64.44,\"quantity\":161,\"active\":false,\"updatedAt\":\"2026-01-28T12:00:00Z\"},{\"id\":32,\"sku\":\"SKU-693431\",\"name\":\"golf mike\",\"status\":\"active\",\"score\":51.6,\"quantity\":213,\"active\":true,\"updatedAt\":\"2026-01-21T12:00:00Z\"},{\"id\":33,\"sku\":\"SKU-170458\",\"name\":\"foxtrot echo\",\"status\":\"failed\",\"score\":3.08,\"quantity\":16,\"active\":true,\"updatedAt\":\"2026-06-14T12:00:00Z\"},{\"id\":34,\"sku\":\"SKU-575652\",\"name\":\"quebec echo\",\"status\":\"archived\",\"score\":7.45,\"quantity\":297,\"active\":true,\"updatedAt\":\"2026-06-02T12:00:00Z\"},{\"id\":35,\"sku\":\"SKU-570174\",\"name\":\"india bravo\",\"status\":\"failed\",\"score\":27.28,\"quantity\":77,\"active\":false,\"updatedAt\":\"2026-03-23T12:00:00Z\"},{\"id\":36,\"sku\":\"SKU-823612\",\"name\":\"uniform sierra\",\"status\":\"archived\",\"score\":86.48,\"quantity\":318,\"active\":true,\"updatedAt\":\"2026-05-09T12:00:00Z\"},{\"id\":37,\"sku\":\"SKU-403326\",\"name\":\"papa hotel\",\"status\":\"active\",\"score\":49.65,\"quantity\":299,\"active\":true,\"updatedAt\":\"2026-03-20T12:00:00Z\"},{\"id\":38,\"sku\":\"SKU-241598\",\"name\":\"romeo mike\",\"status\":\"pending\",\"score\":39.17,\"quantity\":407,\"active\":false,\"updatedAt\":\"2026-04-16T12:00:00Z\"},{\"id\":39,\"sku\":\"SKU-659493\",\"name\":\"papa bravo\",\"status\":\"pending\",\"score\":94.52,\"quantity\":356,\"active\":true,\"updatedAt\":\"2026-05-09T12:00:00Z\"},{\"id\":40,\"sku\":\"SKU-975648\",\"name\":\"bravo sierra\",\"status\":\"pending\",\"score\":22.45,\"quantity\":246,\"active\":false,\"updatedAt\":\"2026-06-28T12:00:00Z\"},{\"id\":41,\"sku\":\"SKU-534571\",\"name\":\"whiskey delta\",\"status\":\"pending\",\"score\":17.4,\"quantity\":21,\"active\":true,\"updatedAt\":\"2026-05-07T12:00:00Z\"},{\"id\":42,\"sku\":\"SKU-6562\",\"name\":\"lima papa\",\"status\":\"pending\",\"score\":53.6,\"quantity\":470,\"active\":false,\"updatedAt\":\"2026-04-20T12:00:00Z\"},{\"id\":43,\"sku\":\"SKU-300056\",\"name\":\"charlie papa\",\"status\":\"active\",\"score\":54.68,\"quantity\":226,\"active\":true,\"updatedAt\":\"2026-05-21T12:00:00Z\"},{\"id\":44,\"sku\":\"SKU-575007\",\"name\":\"sierra mike\",\"status\":\"failed\",\"score\":61.37,\"quantity\":307,\"active\":true,\"updatedAt\":\"2026-05-22T12:00:00Z\"},{\"id\":45,\"sku\":\"SKU-722145\",\"name\":\"victor uniform\",\"status\":\"archived\",\"score\":62.68,\"quantity\":75,\"active\":false,\"updatedAt\":\"2026-06-06T12:00:00Z\"},{\"id\":46,\"sku\":\"SKU-905940\",\"name\":\"alpha romeo\",\"status\":\"archived\",\"score\":82.48,\"quantity\":135,\"active\":false,\"updatedAt\":\"2026-02-20T12:00:00Z\"},{\"id\":47,\"sku\":\"SKU-645839\",\"name\":\"hotel charlie\",\"status\":\"pending\",\"score\":93.84,\"quantity\":47,\"active\":false,\"updatedAt\":\"2026-02-21T12:00:00Z\"},{\"id\":48,\"sku\":\"SKU-524\",\"name\":\"romeo alpha\",\"status\":\"failed\",\"score\":50.78,\"quantity\":485,\"active\":true,\"updatedAt\":\"2026-03-22T12:00:00Z\"},{\"id\":49,\"sku\":\"SKU-274904\",\"name\":\"india papa\",\"status\":\"failed\",\"score\":53.95,\"quantity\":369,\"active\":false,\"updatedAt\":\"2026-04-07T12:00:00Z\"},{\"id\":50,\"sku\":\"SKU-63590\",\"name\":\"juliet foxtrot\",\"status\":\"failed\",\"score\":16.3,\"quantity\":162,\"active\":false,\"updatedAt\":\"2026-03-27T12:00:00Z\"},{\"id\":51,\"sku\":\"SKU-888095\",\"name\":\"oscar romeo\",\"status\":\"failed\",\"score\":1.55,\"quantity\":98,\"active\":false,\"updatedAt\":\"2026-03-13T12:00:00Z\"},{\"id\":52,\"sku\":\"SKU-152688\",\"name\":\"kilo india\",\"status\":\"active\",\"score\":0.62,\"quantity\":317,\"active\":false,\"updatedAt\":\"2026-02-02T12:00:00Z\"},{\"id\":53,\"sku\":\"SKU-438587\",\"name\":\"sierra uniform\",\"status\":\"archived\",\"score\":71.89,\"quantity\":492,\"active\":true,\"updatedAt\":\"2026-03-25T12:00:00Z\"},{\"id\":54,\"sku\":\"SKU-816723\",\"name\":\"tango whiskey\",\"status\":\"archived\",\"score\":70.58,\"quantity\":160,\"active\":false,\"updatedAt\":\"2026-05-10T12:00:00Z\"},{\"id\":55,\"sku\":\"SKU-762042\",\"name\":\"bravo november\",\"status\":\"archived\",\"score\":23.99,\"quantity\":229,\"active\":true,\"updatedAt\":\"2026-06-09T12:00:00Z\"},{\"id\":56,\"sku\":\"SKU-14029\",\"name\":\"november lima\",\"status\":\"active\",\"score\":5.02,\"quantity\":201,\"active\":false,\"updatedAt\":\"2026-05-21T12:00:00Z\"},{\"id\":57,\"sku\":\"SKU-798172\",\"name\":\"sierra sierra\",\"status\":\"archived\",\"score\":92.92,\"quantity\":455,\"active\":true,\"updatedAt\":\"2026-06-26T12:00:00Z\"},{\"id\":58,\"sku\":\"SKU-338632\",\"name\":\"victor tango\",\"status\":\"pending\",\"score\":39.96,\"quantity\":140,\"active\":false,\"updatedAt\":\"2026-06-21T12:00:00Z\"},{\"id\":59,\"sku\":\"SKU-344716\",\"name\":\"sierra romeo\",\"status\":\"archived\",\"score\":72.13,\"quantity\":310,\"active\":true,\"updatedAt\":\"2026-01-23T12:00:00Z\"},{\"id\":60,\"sku\":\"SKU-917943\",\"name\":\"echo juliet\",\"status\":\"archived\",\"score\":77.74,\"quantity\":255,\"active\":false,\"updatedAt\":\"2026-06-28T12:00:00Z\"},{\"id\":61,\"sku\":\"SKU-150627\",\"name\":\"papa foxtrot\",\"status\":\"archived\",\"score\":47.01,\"quantity\":272,\"active\":false,\"updatedAt\":\"2026-05-17T12:00:00Z\"},{\"id\":62,\"sku\":\"SKU-838601\",\"name\":\"charlie echo\",\"status\":\"failed\",\"score\":7.23,\"quantity\":408,\"active\":false,\"updatedAt\":\"2026-04-10T12:00:00Z\"},{\"id\":63,\"sku\":\"SKU-809961\",\"name\":\"foxtrot delta\",\"status\":\"pending\",\"score\":60.76,\"quantity\":51,\"active\":true,\"updatedAt\":\"2026-01-18T12:00:00Z\"},{\"id\":64,\"sku\":\"SKU-513516\",\"name\":\"echo tango\",\"status\":\"failed\",\"score\":67.25,\"quantity\":16,\"active\":false,\"updatedAt\":\"2026-04-07T12:00:00Z\"},{\"id\":65,\"sku\":\"SKU-304984\",\"name\":\"kilo echo\",\"status\":\"active\",\"score\":47.84,\"quantity\":344,\"active\":true,\"updatedAt\":\"2026-04-19T12:00:00Z\"},{\"id\":66,\"sku\":\"SKU-469514\",\"name\":\"india charlie\",\"status\":\"pending\",\"score\":57.19,\"quantity\":309,\"active\":true,\"updatedAt\":\"2026-01-08T12:00:00Z\"},{\"id\":67,\"sku\":\"SKU-71481\",\"name\":\"lima romeo\",\"status\":\"failed\",\"score\":8.23,\"quantity\":384,\"active\":true,\"updatedAt\":\"2026-03-25T12:00:00Z\"},{\"id\":68,\"sku\":\"SKU-836554\",\"name\":\"lima bravo\",\"status\":\"active\",\"score\":80.91,\"quantity\":72,\"active\":false,\"updatedAt\":\"2026-05-19T12:00:00Z\"},{\"id\":69,\"sku\":\"SKU-410694\",\"name\":\"mike sierra\",\"status\":\"archived\",\"score\":66.44,\"quantity\":144,\"active\":false,\"updatedAt\":\"2026-06-24T12:00:00Z\"}]", + "unwrap": true, + "expected": { + "normalized": "[{\"id\":0,\"sku\":\"SKU-11704\",\"name\":\"bravo zulu\",\"status\":\"archived\",\"score\":52.14,\"quantity\":202,\"active\":false,\"updatedAt\":\"2026-02-16T12:00:00Z\"},{\"id\":1,\"sku\":\"SKU-729822\",\"name\":\"golf delta\",\"status\":\"failed\",\"score\":51.84,\"quantity\":98,\"active\":false,\"updatedAt\":\"2026-02-15T12:00:00Z\"},{\"id\":2,\"sku\":\"SKU-296330\",\"name\":\"zulu foxtrot\",\"status\":\"failed\",\"score\":19.08,\"quantity\":71,\"active\":false,\"updatedAt\":\"2026-02-16T12:00:00Z\"},{\"id\":3,\"sku\":\"SKU-761873\",\"name\":\"bravo victor\",\"status\":\"archived\",\"score\":62.16,\"quantity\":340,\"active\":true,\"updatedAt\":\"2026-04-26T12:00:00Z\"},{\"id\":4,\"sku\":\"SKU-827628\",\"name\":\"alpha echo\",\"status\":\"pending\",\"score\":42.53,\"quantity\":10,\"active\":false,\"updatedAt\":\"2026-05-11T12:00:00Z\"},{\"id\":5,\"sku\":\"SKU-426715\",\"name\":\"lima whiskey\",\"status\":\"archived\",\"score\":68.83,\"quantity\":165,\"active\":false,\"updatedAt\":\"2026-01-04T12:00:00Z\"},{\"id\":6,\"sku\":\"SKU-913034\",\"name\":\"hotel echo\",\"status\":\"archived\",\"score\":33.68,\"quantity\":21,\"active\":true,\"updatedAt\":\"2026-06-16T12:00:00Z\"},{\"id\":7,\"sku\":\"SKU-826577\",\"name\":\"papa whiskey\",\"status\":\"active\",\"score\":52.99,\"quantity\":324,\"active\":true,\"updatedAt\":\"2026-06-24T12:00:00Z\"},{\"id\":8,\"sku\":\"SKU-594234\",\"name\":\"zulu zulu\",\"status\":\"archived\",\"score\":81.85,\"quantity\":2,\"active\":true,\"updatedAt\":\"2026-02-21T12:00:00Z\"},{\"id\":9,\"sku\":\"SKU-549495\",\"name\":\"sierra zulu\",\"status\":\"archived\",\"score\":82.32,\"quantity\":214,\"active\":true,\"updatedAt\":\"2026-05-16T12:00:00Z\"},{\"id\":10,\"sku\":\"SKU-312383\",\"name\":\"foxtrot alpha\",\"status\":\"active\",\"score\":94.34,\"quantity\":416,\"active\":true,\"updatedAt\":\"2026-01-18T12:00:00Z\"},{\"id\":11,\"sku\":\"SKU-594896\",\"name\":\"romeo zulu\",\"status\":\"pending\",\"score\":98.63,\"quantity\":324,\"active\":true,\"updatedAt\":\"2026-04-24T12:00:00Z\"},{\"id\":12,\"sku\":\"SKU-139064\",\"name\":\"zulu mike\",\"status\":\"archived\",\"score\":16.09,\"quantity\":198,\"active\":false,\"updatedAt\":\"2026-05-25T12:00:00Z\"},{\"id\":13,\"sku\":\"SKU-735919\",\"name\":\"papa zulu\",\"status\":\"archived\",\"score\":73.65,\"quantity\":477,\"active\":false,\"updatedAt\":\"2026-02-17T12:00:00Z\"},{\"id\":14,\"sku\":\"SKU-795681\",\"name\":\"uniform echo\",\"status\":\"archived\",\"score\":47.77,\"quantity\":262,\"active\":false,\"updatedAt\":\"2026-02-13T12:00:00Z\"},{\"id\":15,\"sku\":\"SKU-477512\",\"name\":\"echo alpha\",\"status\":\"active\",\"score\":2.41,\"quantity\":53,\"active\":true,\"updatedAt\":\"2026-06-02T12:00:00Z\"},{\"id\":16,\"sku\":\"SKU-174454\",\"name\":\"juliet lima\",\"status\":\"archived\",\"score\":36.82,\"quantity\":180,\"active\":true,\"updatedAt\":\"2026-05-23T12:00:00Z\"},{\"id\":17,\"sku\":\"SKU-529989\",\"name\":\"tango juliet\",\"status\":\"archived\",\"score\":29.26,\"quantity\":263,\"active\":true,\"updatedAt\":\"2026-01-08T12:00:00Z\"},{\"id\":18,\"sku\":\"SKU-826842\",\"name\":\"tango sierra\",\"status\":\"pending\",\"score\":35.69,\"quantity\":460,\"active\":false,\"updatedAt\":\"2026-03-14T12:00:00Z\"},{\"id\":19,\"sku\":\"SKU-830306\",\"name\":\"juliet zulu\",\"status\":\"archived\",\"score\":43.63,\"quantity\":169,\"active\":true,\"updatedAt\":\"2026-02-25T12:00:00Z\"},{\"id\":20,\"sku\":\"SKU-572258\",\"name\":\"zulu uniform\",\"status\":\"pending\",\"score\":43.68,\"quantity\":348,\"active\":true,\"updatedAt\":\"2026-04-19T12:00:00Z\"},{\"id\":21,\"sku\":\"SKU-162384\",\"name\":\"quebec juliet\",\"status\":\"archived\",\"score\":85.6,\"quantity\":191,\"active\":false,\"updatedAt\":\"2026-01-20T12:00:00Z\"},{\"id\":22,\"sku\":\"SKU-732128\",\"name\":\"zulu hotel\",\"status\":\"pending\",\"score\":6.74,\"quantity\":360,\"active\":true,\"updatedAt\":\"2026-05-24T12:00:00Z\"},{\"id\":23,\"sku\":\"SKU-962738\",\"name\":\"foxtrot zulu\",\"status\":\"pending\",\"score\":53.84,\"quantity\":195,\"active\":true,\"updatedAt\":\"2026-05-18T12:00:00Z\"},{\"id\":24,\"sku\":\"SKU-725523\",\"name\":\"oscar oscar\",\"status\":\"pending\",\"score\":83.19,\"quantity\":241,\"active\":false,\"updatedAt\":\"2026-05-13T12:00:00Z\"},{\"id\":25,\"sku\":\"SKU-559534\",\"name\":\"november victor\",\"status\":\"failed\",\"score\":15.33,\"quantity\":50,\"active\":false,\"updatedAt\":\"2026-04-11T12:00:00Z\"},{\"id\":26,\"sku\":\"SKU-910057\",\"name\":\"uniform mike\",\"status\":\"failed\",\"score\":27.87,\"quantity\":246,\"active\":true,\"updatedAt\":\"2026-05-23T12:00:00Z\"},{\"id\":27,\"sku\":\"SKU-333534\",\"name\":\"victor zulu\",\"status\":\"failed\",\"score\":0.27,\"quantity\":83,\"active\":true,\"updatedAt\":\"2026-01-03T12:00:00Z\"},{\"id\":28,\"sku\":\"SKU-911823\",\"name\":\"golf oscar\",\"status\":\"pending\",\"score\":79.39,\"quantity\":359,\"active\":false,\"updatedAt\":\"2026-03-28T12:00:00Z\"},{\"id\":29,\"sku\":\"SKU-82374\",\"name\":\"charlie juliet\",\"status\":\"failed\",\"score\":69.13,\"quantity\":135,\"active\":false,\"updatedAt\":\"2026-01-14T12:00:00Z\"},{\"id\":30,\"sku\":\"SKU-437150\",\"name\":\"november echo\",\"status\":\"failed\",\"score\":30.32,\"quantity\":345,\"active\":true,\"updatedAt\":\"2026-01-06T12:00:00Z\"},{\"id\":31,\"sku\":\"SKU-717183\",\"name\":\"victor golf\",\"status\":\"failed\",\"score\":64.44,\"quantity\":161,\"active\":false,\"updatedAt\":\"2026-01-28T12:00:00Z\"},{\"id\":32,\"sku\":\"SKU-693431\",\"name\":\"golf mike\",\"status\":\"active\",\"score\":51.6,\"quantity\":213,\"active\":true,\"updatedAt\":\"2026-01-21T12:00:00Z\"},{\"id\":33,\"sku\":\"SKU-170458\",\"name\":\"foxtrot echo\",\"status\":\"failed\",\"score\":3.08,\"quantity\":16,\"active\":true,\"updatedAt\":\"2026-06-14T12:00:00Z\"},{\"id\":34,\"sku\":\"SKU-575652\",\"name\":\"quebec echo\",\"status\":\"archived\",\"score\":7.45,\"quantity\":297,\"active\":true,\"updatedAt\":\"2026-06-02T12:00:00Z\"},{\"id\":35,\"sku\":\"SKU-570174\",\"name\":\"india bravo\",\"status\":\"failed\",\"score\":27.28,\"quantity\":77,\"active\":false,\"updatedAt\":\"2026-03-23T12:00:00Z\"},{\"id\":36,\"sku\":\"SKU-823612\",\"name\":\"uniform sierra\",\"status\":\"archived\",\"score\":86.48,\"quantity\":318,\"active\":true,\"updatedAt\":\"2026-05-09T12:00:00Z\"},{\"id\":37,\"sku\":\"SKU-403326\",\"name\":\"papa hotel\",\"status\":\"active\",\"score\":49.65,\"quantity\":299,\"active\":true,\"updatedAt\":\"2026-03-20T12:00:00Z\"},{\"id\":38,\"sku\":\"SKU-241598\",\"name\":\"romeo mike\",\"status\":\"pending\",\"score\":39.17,\"quantity\":407,\"active\":false,\"updatedAt\":\"2026-04-16T12:00:00Z\"},{\"id\":39,\"sku\":\"SKU-659493\",\"name\":\"papa bravo\",\"status\":\"pending\",\"score\":94.52,\"quantity\":356,\"active\":true,\"updatedAt\":\"2026-05-09T12:00:00Z\"},{\"id\":40,\"sku\":\"SKU-975648\",\"name\":\"bravo sierra\",\"status\":\"pending\",\"score\":22.45,\"quantity\":246,\"active\":false,\"updatedAt\":\"2026-06-28T12:00:00Z\"},{\"id\":41,\"sku\":\"SKU-534571\",\"name\":\"whiskey delta\",\"status\":\"pending\",\"score\":17.4,\"quantity\":21,\"active\":true,\"updatedAt\":\"2026-05-07T12:00:00Z\"},{\"id\":42,\"sku\":\"SKU-6562\",\"name\":\"lima papa\",\"status\":\"pending\",\"score\":53.6,\"quantity\":470,\"active\":false,\"updatedAt\":\"2026-04-20T12:00:00Z\"},{\"id\":43,\"sku\":\"SKU-300056\",\"name\":\"charlie papa\",\"status\":\"active\",\"score\":54.68,\"quantity\":226,\"active\":true,\"updatedAt\":\"2026-05-21T12:00:00Z\"},{\"id\":44,\"sku\":\"SKU-575007\",\"name\":\"sierra mike\",\"status\":\"failed\",\"score\":61.37,\"quantity\":307,\"active\":true,\"updatedAt\":\"2026-05-22T12:00:00Z\"},{\"id\":45,\"sku\":\"SKU-722145\",\"name\":\"victor uniform\",\"status\":\"archived\",\"score\":62.68,\"quantity\":75,\"active\":false,\"updatedAt\":\"2026-06-06T12:00:00Z\"},{\"id\":46,\"sku\":\"SKU-905940\",\"name\":\"alpha romeo\",\"status\":\"archived\",\"score\":82.48,\"quantity\":135,\"active\":false,\"updatedAt\":\"2026-02-20T12:00:00Z\"},{\"id\":47,\"sku\":\"SKU-645839\",\"name\":\"hotel charlie\",\"status\":\"pending\",\"score\":93.84,\"quantity\":47,\"active\":false,\"updatedAt\":\"2026-02-21T12:00:00Z\"},{\"id\":48,\"sku\":\"SKU-524\",\"name\":\"romeo alpha\",\"status\":\"failed\",\"score\":50.78,\"quantity\":485,\"active\":true,\"updatedAt\":\"2026-03-22T12:00:00Z\"},{\"id\":49,\"sku\":\"SKU-274904\",\"name\":\"india papa\",\"status\":\"failed\",\"score\":53.95,\"quantity\":369,\"active\":false,\"updatedAt\":\"2026-04-07T12:00:00Z\"},{\"id\":50,\"sku\":\"SKU-63590\",\"name\":\"juliet foxtrot\",\"status\":\"failed\",\"score\":16.3,\"quantity\":162,\"active\":false,\"updatedAt\":\"2026-03-27T12:00:00Z\"},{\"id\":51,\"sku\":\"SKU-888095\",\"name\":\"oscar romeo\",\"status\":\"failed\",\"score\":1.55,\"quantity\":98,\"active\":false,\"updatedAt\":\"2026-03-13T12:00:00Z\"},{\"id\":52,\"sku\":\"SKU-152688\",\"name\":\"kilo india\",\"status\":\"active\",\"score\":0.62,\"quantity\":317,\"active\":false,\"updatedAt\":\"2026-02-02T12:00:00Z\"},{\"id\":53,\"sku\":\"SKU-438587\",\"name\":\"sierra uniform\",\"status\":\"archived\",\"score\":71.89,\"quantity\":492,\"active\":true,\"updatedAt\":\"2026-03-25T12:00:00Z\"},{\"id\":54,\"sku\":\"SKU-816723\",\"name\":\"tango whiskey\",\"status\":\"archived\",\"score\":70.58,\"quantity\":160,\"active\":false,\"updatedAt\":\"2026-05-10T12:00:00Z\"},{\"id\":55,\"sku\":\"SKU-762042\",\"name\":\"bravo november\",\"status\":\"archived\",\"score\":23.99,\"quantity\":229,\"active\":true,\"updatedAt\":\"2026-06-09T12:00:00Z\"},{\"id\":56,\"sku\":\"SKU-14029\",\"name\":\"november lima\",\"status\":\"active\",\"score\":5.02,\"quantity\":201,\"active\":false,\"updatedAt\":\"2026-05-21T12:00:00Z\"},{\"id\":57,\"sku\":\"SKU-798172\",\"name\":\"sierra sierra\",\"status\":\"archived\",\"score\":92.92,\"quantity\":455,\"active\":true,\"updatedAt\":\"2026-06-26T12:00:00Z\"},{\"id\":58,\"sku\":\"SKU-338632\",\"name\":\"victor tango\",\"status\":\"pending\",\"score\":39.96,\"quantity\":140,\"active\":false,\"updatedAt\":\"2026-06-21T12:00:00Z\"},{\"id\":59,\"sku\":\"SKU-344716\",\"name\":\"sierra romeo\",\"status\":\"archived\",\"score\":72.13,\"quantity\":310,\"active\":true,\"updatedAt\":\"2026-01-23T12:00:00Z\"},{\"id\":60,\"sku\":\"SKU-917943\",\"name\":\"echo juliet\",\"status\":\"archived\",\"score\":77.74,\"quantity\":255,\"active\":false,\"updatedAt\":\"2026-06-28T12:00:00Z\"},{\"id\":61,\"sku\":\"SKU-150627\",\"name\":\"papa foxtrot\",\"status\":\"archived\",\"score\":47.01,\"quantity\":272,\"active\":false,\"updatedAt\":\"2026-05-17T12:00:00Z\"},{\"id\":62,\"sku\":\"SKU-838601\",\"name\":\"charlie echo\",\"status\":\"failed\",\"score\":7.23,\"quantity\":408,\"active\":false,\"updatedAt\":\"2026-04-10T12:00:00Z\"},{\"id\":63,\"sku\":\"SKU-809961\",\"name\":\"foxtrot delta\",\"status\":\"pending\",\"score\":60.76,\"quantity\":51,\"active\":true,\"updatedAt\":\"2026-01-18T12:00:00Z\"},{\"id\":64,\"sku\":\"SKU-513516\",\"name\":\"echo tango\",\"status\":\"failed\",\"score\":67.25,\"quantity\":16,\"active\":false,\"updatedAt\":\"2026-04-07T12:00:00Z\"},{\"id\":65,\"sku\":\"SKU-304984\",\"name\":\"kilo echo\",\"status\":\"active\",\"score\":47.84,\"quantity\":344,\"active\":true,\"updatedAt\":\"2026-04-19T12:00:00Z\"},{\"id\":66,\"sku\":\"SKU-469514\",\"name\":\"india charlie\",\"status\":\"pending\",\"score\":57.19,\"quantity\":309,\"active\":true,\"updatedAt\":\"2026-01-08T12:00:00Z\"},{\"id\":67,\"sku\":\"SKU-71481\",\"name\":\"lima romeo\",\"status\":\"failed\",\"score\":8.23,\"quantity\":384,\"active\":true,\"updatedAt\":\"2026-03-25T12:00:00Z\"},{\"id\":68,\"sku\":\"SKU-836554\",\"name\":\"lima bravo\",\"status\":\"active\",\"score\":80.91,\"quantity\":72,\"active\":false,\"updatedAt\":\"2026-05-19T12:00:00Z\"},{\"id\":69,\"sku\":\"SKU-410694\",\"name\":\"mike sierra\",\"status\":\"archived\",\"score\":66.44,\"quantity\":144,\"active\":false,\"updatedAt\":\"2026-06-24T12:00:00Z\"}]", + "encoded": "[70]{id,sku,name,status,score,quantity,active,updatedAt}:\n 0,\"SKU-11704\",bravo zulu,archived,52.14,202,false,\"2026-02-16T12:00:00Z\"\n 1,\"SKU-729822\",golf delta,failed,51.84,98,false,\"2026-02-15T12:00:00Z\"\n 2,\"SKU-296330\",zulu foxtrot,failed,19.08,71,false,\"2026-02-16T12:00:00Z\"\n 3,\"SKU-761873\",bravo victor,archived,62.16,340,true,\"2026-04-26T12:00:00Z\"\n 4,\"SKU-827628\",alpha echo,pending,42.53,10,false,\"2026-05-11T12:00:00Z\"\n 5,\"SKU-426715\",lima whiskey,archived,68.83,165,false,\"2026-01-04T12:00:00Z\"\n 6,\"SKU-913034\",hotel echo,archived,33.68,21,true,\"2026-06-16T12:00:00Z\"\n 7,\"SKU-826577\",papa whiskey,active,52.99,324,true,\"2026-06-24T12:00:00Z\"\n 8,\"SKU-594234\",zulu zulu,archived,81.85,2,true,\"2026-02-21T12:00:00Z\"\n 9,\"SKU-549495\",sierra zulu,archived,82.32,214,true,\"2026-05-16T12:00:00Z\"\n 10,\"SKU-312383\",foxtrot alpha,active,94.34,416,true,\"2026-01-18T12:00:00Z\"\n 11,\"SKU-594896\",romeo zulu,pending,98.63,324,true,\"2026-04-24T12:00:00Z\"\n 12,\"SKU-139064\",zulu mike,archived,16.09,198,false,\"2026-05-25T12:00:00Z\"\n 13,\"SKU-735919\",papa zulu,archived,73.65,477,false,\"2026-02-17T12:00:00Z\"\n 14,\"SKU-795681\",uniform echo,archived,47.77,262,false,\"2026-02-13T12:00:00Z\"\n 15,\"SKU-477512\",echo alpha,active,2.41,53,true,\"2026-06-02T12:00:00Z\"\n 16,\"SKU-174454\",juliet lima,archived,36.82,180,true,\"2026-05-23T12:00:00Z\"\n 17,\"SKU-529989\",tango juliet,archived,29.26,263,true,\"2026-01-08T12:00:00Z\"\n 18,\"SKU-826842\",tango sierra,pending,35.69,460,false,\"2026-03-14T12:00:00Z\"\n 19,\"SKU-830306\",juliet zulu,archived,43.63,169,true,\"2026-02-25T12:00:00Z\"\n 20,\"SKU-572258\",zulu uniform,pending,43.68,348,true,\"2026-04-19T12:00:00Z\"\n 21,\"SKU-162384\",quebec juliet,archived,85.6,191,false,\"2026-01-20T12:00:00Z\"\n 22,\"SKU-732128\",zulu hotel,pending,6.74,360,true,\"2026-05-24T12:00:00Z\"\n 23,\"SKU-962738\",foxtrot zulu,pending,53.84,195,true,\"2026-05-18T12:00:00Z\"\n 24,\"SKU-725523\",oscar oscar,pending,83.19,241,false,\"2026-05-13T12:00:00Z\"\n 25,\"SKU-559534\",november victor,failed,15.33,50,false,\"2026-04-11T12:00:00Z\"\n 26,\"SKU-910057\",uniform mike,failed,27.87,246,true,\"2026-05-23T12:00:00Z\"\n 27,\"SKU-333534\",victor zulu,failed,0.27,83,true,\"2026-01-03T12:00:00Z\"\n 28,\"SKU-911823\",golf oscar,pending,79.39,359,false,\"2026-03-28T12:00:00Z\"\n 29,\"SKU-82374\",charlie juliet,failed,69.13,135,false,\"2026-01-14T12:00:00Z\"\n 30,\"SKU-437150\",november echo,failed,30.32,345,true,\"2026-01-06T12:00:00Z\"\n 31,\"SKU-717183\",victor golf,failed,64.44,161,false,\"2026-01-28T12:00:00Z\"\n 32,\"SKU-693431\",golf mike,active,51.6,213,true,\"2026-01-21T12:00:00Z\"\n 33,\"SKU-170458\",foxtrot echo,failed,3.08,16,true,\"2026-06-14T12:00:00Z\"\n 34,\"SKU-575652\",quebec echo,archived,7.45,297,true,\"2026-06-02T12:00:00Z\"\n 35,\"SKU-570174\",india bravo,failed,27.28,77,false,\"2026-03-23T12:00:00Z\"\n 36,\"SKU-823612\",uniform sierra,archived,86.48,318,true,\"2026-05-09T12:00:00Z\"\n 37,\"SKU-403326\",papa hotel,active,49.65,299,true,\"2026-03-20T12:00:00Z\"\n 38,\"SKU-241598\",romeo mike,pending,39.17,407,false,\"2026-04-16T12:00:00Z\"\n 39,\"SKU-659493\",papa bravo,pending,94.52,356,true,\"2026-05-09T12:00:00Z\"\n 40,\"SKU-975648\",bravo sierra,pending,22.45,246,false,\"2026-06-28T12:00:00Z\"\n 41,\"SKU-534571\",whiskey delta,pending,17.4,21,true,\"2026-05-07T12:00:00Z\"\n 42,\"SKU-6562\",lima papa,pending,53.6,470,false,\"2026-04-20T12:00:00Z\"\n 43,\"SKU-300056\",charlie papa,active,54.68,226,true,\"2026-05-21T12:00:00Z\"\n 44,\"SKU-575007\",sierra mike,failed,61.37,307,true,\"2026-05-22T12:00:00Z\"\n 45,\"SKU-722145\",victor uniform,archived,62.68,75,false,\"2026-06-06T12:00:00Z\"\n 46,\"SKU-905940\",alpha romeo,archived,82.48,135,false,\"2026-02-20T12:00:00Z\"\n 47,\"SKU-645839\",hotel charlie,pending,93.84,47,false,\"2026-02-21T12:00:00Z\"\n 48,\"SKU-524\",romeo alpha,failed,50.78,485,true,\"2026-03-22T12:00:00Z\"\n 49,\"SKU-274904\",india papa,failed,53.95,369,false,\"2026-04-07T12:00:00Z\"\n 50,\"SKU-63590\",juliet foxtrot,failed,16.3,162,false,\"2026-03-27T12:00:00Z\"\n 51,\"SKU-888095\",oscar romeo,failed,1.55,98,false,\"2026-03-13T12:00:00Z\"\n 52,\"SKU-152688\",kilo india,active,0.62,317,false,\"2026-02-02T12:00:00Z\"\n 53,\"SKU-438587\",sierra uniform,archived,71.89,492,true,\"2026-03-25T12:00:00Z\"\n 54,\"SKU-816723\",tango whiskey,archived,70.58,160,false,\"2026-05-10T12:00:00Z\"\n 55,\"SKU-762042\",bravo november,archived,23.99,229,true,\"2026-06-09T12:00:00Z\"\n 56,\"SKU-14029\",november lima,active,5.02,201,false,\"2026-05-21T12:00:00Z\"\n 57,\"SKU-798172\",sierra sierra,archived,92.92,455,true,\"2026-06-26T12:00:00Z\"\n 58,\"SKU-338632\",victor tango,pending,39.96,140,false,\"2026-06-21T12:00:00Z\"\n 59,\"SKU-344716\",sierra romeo,archived,72.13,310,true,\"2026-01-23T12:00:00Z\"\n 60,\"SKU-917943\",echo juliet,archived,77.74,255,false,\"2026-06-28T12:00:00Z\"\n 61,\"SKU-150627\",papa foxtrot,archived,47.01,272,false,\"2026-05-17T12:00:00Z\"\n 62,\"SKU-838601\",charlie echo,failed,7.23,408,false,\"2026-04-10T12:00:00Z\"\n 63,\"SKU-809961\",foxtrot delta,pending,60.76,51,true,\"2026-01-18T12:00:00Z\"\n 64,\"SKU-513516\",echo tango,failed,67.25,16,false,\"2026-04-07T12:00:00Z\"\n 65,\"SKU-304984\",kilo echo,active,47.84,344,true,\"2026-04-19T12:00:00Z\"\n 66,\"SKU-469514\",india charlie,pending,57.19,309,true,\"2026-01-08T12:00:00Z\"\n 67,\"SKU-71481\",lima romeo,failed,8.23,384,true,\"2026-03-25T12:00:00Z\"\n 68,\"SKU-836554\",lima bravo,active,80.91,72,false,\"2026-05-19T12:00:00Z\"\n 69,\"SKU-410694\",mike sierra,archived,66.44,144,false,\"2026-06-24T12:00:00Z\"" + } + }, + { + "name": "bench-10kb-1", + "rawContent": "[{\"id\":0,\"sku\":\"SKU-369981\",\"name\":\"lima oscar\",\"status\":\"failed\",\"score\":9.74,\"quantity\":27,\"active\":false,\"updatedAt\":\"2026-06-24T12:00:00Z\"},{\"id\":1,\"sku\":\"SKU-967340\",\"name\":\"sierra kilo\",\"status\":\"pending\",\"score\":37.31,\"quantity\":46,\"active\":false,\"updatedAt\":\"2026-02-04T12:00:00Z\"},{\"id\":2,\"sku\":\"SKU-334172\",\"name\":\"mike golf\",\"status\":\"archived\",\"score\":94.56,\"quantity\":499,\"active\":false,\"updatedAt\":\"2026-06-24T12:00:00Z\"},{\"id\":3,\"sku\":\"SKU-708572\",\"name\":\"uniform kilo\",\"status\":\"failed\",\"score\":91.72,\"quantity\":414,\"active\":false,\"updatedAt\":\"2026-01-12T12:00:00Z\"},{\"id\":4,\"sku\":\"SKU-59452\",\"name\":\"alpha sierra\",\"status\":\"active\",\"score\":21.56,\"quantity\":215,\"active\":false,\"updatedAt\":\"2026-06-20T12:00:00Z\"},{\"id\":5,\"sku\":\"SKU-354103\",\"name\":\"zulu hotel\",\"status\":\"pending\",\"score\":59.22,\"quantity\":250,\"active\":true,\"updatedAt\":\"2026-05-02T12:00:00Z\"},{\"id\":6,\"sku\":\"SKU-868744\",\"name\":\"whiskey tango\",\"status\":\"active\",\"score\":46.31,\"quantity\":111,\"active\":true,\"updatedAt\":\"2026-01-05T12:00:00Z\"},{\"id\":7,\"sku\":\"SKU-834024\",\"name\":\"delta delta\",\"status\":\"archived\",\"score\":88.68,\"quantity\":221,\"active\":true,\"updatedAt\":\"2026-06-10T12:00:00Z\"},{\"id\":8,\"sku\":\"SKU-4318\",\"name\":\"alpha lima\",\"status\":\"failed\",\"score\":86.59,\"quantity\":237,\"active\":true,\"updatedAt\":\"2026-05-22T12:00:00Z\"},{\"id\":9,\"sku\":\"SKU-397052\",\"name\":\"bravo quebec\",\"status\":\"active\",\"score\":14.17,\"quantity\":244,\"active\":true,\"updatedAt\":\"2026-01-08T12:00:00Z\"},{\"id\":10,\"sku\":\"SKU-987401\",\"name\":\"november sierra\",\"status\":\"failed\",\"score\":67.5,\"quantity\":212,\"active\":true,\"updatedAt\":\"2026-03-22T12:00:00Z\"},{\"id\":11,\"sku\":\"SKU-399261\",\"name\":\"charlie tango\",\"status\":\"active\",\"score\":12.8,\"quantity\":104,\"active\":false,\"updatedAt\":\"2026-05-01T12:00:00Z\"},{\"id\":12,\"sku\":\"SKU-541132\",\"name\":\"india november\",\"status\":\"active\",\"score\":8.18,\"quantity\":21,\"active\":true,\"updatedAt\":\"2026-02-08T12:00:00Z\"},{\"id\":13,\"sku\":\"SKU-664917\",\"name\":\"november golf\",\"status\":\"pending\",\"score\":39.06,\"quantity\":87,\"active\":true,\"updatedAt\":\"2026-02-22T12:00:00Z\"},{\"id\":14,\"sku\":\"SKU-470178\",\"name\":\"papa uniform\",\"status\":\"failed\",\"score\":31.51,\"quantity\":379,\"active\":true,\"updatedAt\":\"2026-04-28T12:00:00Z\"},{\"id\":15,\"sku\":\"SKU-795808\",\"name\":\"papa india\",\"status\":\"active\",\"score\":81.8,\"quantity\":443,\"active\":true,\"updatedAt\":\"2026-02-17T12:00:00Z\"},{\"id\":16,\"sku\":\"SKU-501650\",\"name\":\"golf india\",\"status\":\"pending\",\"score\":94.3,\"quantity\":462,\"active\":true,\"updatedAt\":\"2026-05-11T12:00:00Z\"},{\"id\":17,\"sku\":\"SKU-562629\",\"name\":\"charlie echo\",\"status\":\"archived\",\"score\":34.87,\"quantity\":280,\"active\":true,\"updatedAt\":\"2026-02-21T12:00:00Z\"},{\"id\":18,\"sku\":\"SKU-32189\",\"name\":\"zulu mike\",\"status\":\"pending\",\"score\":58.38,\"quantity\":372,\"active\":false,\"updatedAt\":\"2026-05-13T12:00:00Z\"},{\"id\":19,\"sku\":\"SKU-197718\",\"name\":\"india kilo\",\"status\":\"active\",\"score\":19.81,\"quantity\":296,\"active\":true,\"updatedAt\":\"2026-03-03T12:00:00Z\"},{\"id\":20,\"sku\":\"SKU-770813\",\"name\":\"whiskey delta\",\"status\":\"archived\",\"score\":14.07,\"quantity\":375,\"active\":false,\"updatedAt\":\"2026-06-11T12:00:00Z\"},{\"id\":21,\"sku\":\"SKU-993297\",\"name\":\"lima oscar\",\"status\":\"pending\",\"score\":49.46,\"quantity\":320,\"active\":true,\"updatedAt\":\"2026-06-04T12:00:00Z\"},{\"id\":22,\"sku\":\"SKU-129005\",\"name\":\"india delta\",\"status\":\"active\",\"score\":38.89,\"quantity\":1,\"active\":true,\"updatedAt\":\"2026-06-18T12:00:00Z\"},{\"id\":23,\"sku\":\"SKU-983730\",\"name\":\"bravo papa\",\"status\":\"pending\",\"score\":88.65,\"quantity\":367,\"active\":false,\"updatedAt\":\"2026-01-28T12:00:00Z\"},{\"id\":24,\"sku\":\"SKU-758767\",\"name\":\"romeo tango\",\"status\":\"pending\",\"score\":92.43,\"quantity\":327,\"active\":false,\"updatedAt\":\"2026-04-21T12:00:00Z\"},{\"id\":25,\"sku\":\"SKU-642530\",\"name\":\"charlie whiskey\",\"status\":\"failed\",\"score\":91.16,\"quantity\":139,\"active\":true,\"updatedAt\":\"2026-06-13T12:00:00Z\"},{\"id\":26,\"sku\":\"SKU-465260\",\"name\":\"kilo lima\",\"status\":\"archived\",\"score\":35.1,\"quantity\":257,\"active\":true,\"updatedAt\":\"2026-01-16T12:00:00Z\"},{\"id\":27,\"sku\":\"SKU-73648\",\"name\":\"india mike\",\"status\":\"archived\",\"score\":97.62,\"quantity\":386,\"active\":true,\"updatedAt\":\"2026-05-13T12:00:00Z\"},{\"id\":28,\"sku\":\"SKU-776786\",\"name\":\"victor lima\",\"status\":\"active\",\"score\":27.69,\"quantity\":438,\"active\":true,\"updatedAt\":\"2026-05-27T12:00:00Z\"},{\"id\":29,\"sku\":\"SKU-468983\",\"name\":\"hotel kilo\",\"status\":\"archived\",\"score\":0.48,\"quantity\":239,\"active\":true,\"updatedAt\":\"2026-02-09T12:00:00Z\"},{\"id\":30,\"sku\":\"SKU-563299\",\"name\":\"charlie november\",\"status\":\"archived\",\"score\":13.56,\"quantity\":88,\"active\":true,\"updatedAt\":\"2026-05-08T12:00:00Z\"},{\"id\":31,\"sku\":\"SKU-419442\",\"name\":\"romeo charlie\",\"status\":\"pending\",\"score\":26.86,\"quantity\":346,\"active\":true,\"updatedAt\":\"2026-04-15T12:00:00Z\"},{\"id\":32,\"sku\":\"SKU-182808\",\"name\":\"november tango\",\"status\":\"pending\",\"score\":95.74,\"quantity\":288,\"active\":false,\"updatedAt\":\"2026-04-25T12:00:00Z\"},{\"id\":33,\"sku\":\"SKU-921862\",\"name\":\"zulu sierra\",\"status\":\"active\",\"score\":38.59,\"quantity\":370,\"active\":true,\"updatedAt\":\"2026-02-02T12:00:00Z\"},{\"id\":34,\"sku\":\"SKU-809989\",\"name\":\"golf echo\",\"status\":\"archived\",\"score\":13.09,\"quantity\":427,\"active\":false,\"updatedAt\":\"2026-04-23T12:00:00Z\"},{\"id\":35,\"sku\":\"SKU-684809\",\"name\":\"romeo foxtrot\",\"status\":\"pending\",\"score\":82.4,\"quantity\":444,\"active\":false,\"updatedAt\":\"2026-03-19T12:00:00Z\"},{\"id\":36,\"sku\":\"SKU-56758\",\"name\":\"november victor\",\"status\":\"pending\",\"score\":57.61,\"quantity\":122,\"active\":true,\"updatedAt\":\"2026-02-06T12:00:00Z\"},{\"id\":37,\"sku\":\"SKU-158771\",\"name\":\"hotel quebec\",\"status\":\"archived\",\"score\":52.21,\"quantity\":108,\"active\":true,\"updatedAt\":\"2026-05-13T12:00:00Z\"},{\"id\":38,\"sku\":\"SKU-201657\",\"name\":\"india mike\",\"status\":\"archived\",\"score\":11.98,\"quantity\":279,\"active\":true,\"updatedAt\":\"2026-04-17T12:00:00Z\"},{\"id\":39,\"sku\":\"SKU-934265\",\"name\":\"oscar victor\",\"status\":\"failed\",\"score\":10.39,\"quantity\":10,\"active\":true,\"updatedAt\":\"2026-04-09T12:00:00Z\"},{\"id\":40,\"sku\":\"SKU-980470\",\"name\":\"delta zulu\",\"status\":\"failed\",\"score\":95.34,\"quantity\":73,\"active\":false,\"updatedAt\":\"2026-03-22T12:00:00Z\"},{\"id\":41,\"sku\":\"SKU-522215\",\"name\":\"november lima\",\"status\":\"archived\",\"score\":64.84,\"quantity\":369,\"active\":true,\"updatedAt\":\"2026-05-16T12:00:00Z\"},{\"id\":42,\"sku\":\"SKU-409943\",\"name\":\"hotel zulu\",\"status\":\"archived\",\"score\":11.64,\"quantity\":426,\"active\":true,\"updatedAt\":\"2026-02-11T12:00:00Z\"},{\"id\":43,\"sku\":\"SKU-77806\",\"name\":\"mike lima\",\"status\":\"active\",\"score\":63.83,\"quantity\":203,\"active\":true,\"updatedAt\":\"2026-03-19T12:00:00Z\"},{\"id\":44,\"sku\":\"SKU-928201\",\"name\":\"charlie mike\",\"status\":\"pending\",\"score\":5.06,\"quantity\":450,\"active\":false,\"updatedAt\":\"2026-05-06T12:00:00Z\"},{\"id\":45,\"sku\":\"SKU-619926\",\"name\":\"bravo whiskey\",\"status\":\"pending\",\"score\":20.01,\"quantity\":155,\"active\":true,\"updatedAt\":\"2026-02-06T12:00:00Z\"},{\"id\":46,\"sku\":\"SKU-22057\",\"name\":\"charlie sierra\",\"status\":\"failed\",\"score\":33.47,\"quantity\":435,\"active\":true,\"updatedAt\":\"2026-05-07T12:00:00Z\"},{\"id\":47,\"sku\":\"SKU-638993\",\"name\":\"alpha charlie\",\"status\":\"archived\",\"score\":26.92,\"quantity\":104,\"active\":false,\"updatedAt\":\"2026-05-07T12:00:00Z\"},{\"id\":48,\"sku\":\"SKU-765547\",\"name\":\"echo oscar\",\"status\":\"pending\",\"score\":72.91,\"quantity\":279,\"active\":true,\"updatedAt\":\"2026-02-13T12:00:00Z\"},{\"id\":49,\"sku\":\"SKU-440455\",\"name\":\"echo uniform\",\"status\":\"archived\",\"score\":74.63,\"quantity\":272,\"active\":false,\"updatedAt\":\"2026-06-22T12:00:00Z\"},{\"id\":50,\"sku\":\"SKU-303645\",\"name\":\"papa sierra\",\"status\":\"pending\",\"score\":28.63,\"quantity\":90,\"active\":false,\"updatedAt\":\"2026-06-25T12:00:00Z\"},{\"id\":51,\"sku\":\"SKU-213454\",\"name\":\"november oscar\",\"status\":\"pending\",\"score\":35.5,\"quantity\":159,\"active\":true,\"updatedAt\":\"2026-04-26T12:00:00Z\"},{\"id\":52,\"sku\":\"SKU-715886\",\"name\":\"mike oscar\",\"status\":\"active\",\"score\":73.01,\"quantity\":399,\"active\":true,\"updatedAt\":\"2026-05-21T12:00:00Z\"},{\"id\":53,\"sku\":\"SKU-586691\",\"name\":\"delta zulu\",\"status\":\"archived\",\"score\":48.69,\"quantity\":373,\"active\":false,\"updatedAt\":\"2026-02-10T12:00:00Z\"},{\"id\":54,\"sku\":\"SKU-236865\",\"name\":\"oscar romeo\",\"status\":\"archived\",\"score\":66.72,\"quantity\":241,\"active\":false,\"updatedAt\":\"2026-01-07T12:00:00Z\"},{\"id\":55,\"sku\":\"SKU-157251\",\"name\":\"alpha delta\",\"status\":\"archived\",\"score\":20.83,\"quantity\":58,\"active\":true,\"updatedAt\":\"2026-04-16T12:00:00Z\"},{\"id\":56,\"sku\":\"SKU-262253\",\"name\":\"golf echo\",\"status\":\"failed\",\"score\":33.33,\"quantity\":168,\"active\":false,\"updatedAt\":\"2026-03-12T12:00:00Z\"},{\"id\":57,\"sku\":\"SKU-863890\",\"name\":\"victor whiskey\",\"status\":\"archived\",\"score\":59.55,\"quantity\":199,\"active\":true,\"updatedAt\":\"2026-02-12T12:00:00Z\"},{\"id\":58,\"sku\":\"SKU-602861\",\"name\":\"bravo romeo\",\"status\":\"archived\",\"score\":62.56,\"quantity\":107,\"active\":false,\"updatedAt\":\"2026-02-14T12:00:00Z\"},{\"id\":59,\"sku\":\"SKU-140459\",\"name\":\"tango oscar\",\"status\":\"pending\",\"score\":6.22,\"quantity\":52,\"active\":false,\"updatedAt\":\"2026-01-21T12:00:00Z\"},{\"id\":60,\"sku\":\"SKU-736207\",\"name\":\"victor delta\",\"status\":\"active\",\"score\":44.62,\"quantity\":24,\"active\":true,\"updatedAt\":\"2026-03-13T12:00:00Z\"},{\"id\":61,\"sku\":\"SKU-759724\",\"name\":\"delta juliet\",\"status\":\"pending\",\"score\":26.46,\"quantity\":384,\"active\":false,\"updatedAt\":\"2026-05-19T12:00:00Z\"},{\"id\":62,\"sku\":\"SKU-442602\",\"name\":\"alpha golf\",\"status\":\"archived\",\"score\":49.58,\"quantity\":228,\"active\":true,\"updatedAt\":\"2026-05-05T12:00:00Z\"},{\"id\":63,\"sku\":\"SKU-575268\",\"name\":\"romeo foxtrot\",\"status\":\"archived\",\"score\":14.2,\"quantity\":267,\"active\":true,\"updatedAt\":\"2026-05-05T12:00:00Z\"},{\"id\":64,\"sku\":\"SKU-648994\",\"name\":\"hotel romeo\",\"status\":\"archived\",\"score\":44.89,\"quantity\":321,\"active\":false,\"updatedAt\":\"2026-03-11T12:00:00Z\"},{\"id\":65,\"sku\":\"SKU-188457\",\"name\":\"mike echo\",\"status\":\"active\",\"score\":94.79,\"quantity\":432,\"active\":false,\"updatedAt\":\"2026-05-21T12:00:00Z\"},{\"id\":66,\"sku\":\"SKU-149621\",\"name\":\"golf november\",\"status\":\"pending\",\"score\":90.25,\"quantity\":465,\"active\":false,\"updatedAt\":\"2026-03-11T12:00:00Z\"},{\"id\":67,\"sku\":\"SKU-285740\",\"name\":\"romeo charlie\",\"status\":\"pending\",\"score\":93.89,\"quantity\":5,\"active\":false,\"updatedAt\":\"2026-04-11T12:00:00Z\"},{\"id\":68,\"sku\":\"SKU-291992\",\"name\":\"sierra lima\",\"status\":\"active\",\"score\":86.27,\"quantity\":85,\"active\":false,\"updatedAt\":\"2026-02-07T12:00:00Z\"},{\"id\":69,\"sku\":\"SKU-747919\",\"name\":\"golf whiskey\",\"status\":\"failed\",\"score\":90.75,\"quantity\":226,\"active\":false,\"updatedAt\":\"2026-04-09T12:00:00Z\"}]", + "unwrap": true, + "expected": { + "normalized": "[{\"id\":0,\"sku\":\"SKU-369981\",\"name\":\"lima oscar\",\"status\":\"failed\",\"score\":9.74,\"quantity\":27,\"active\":false,\"updatedAt\":\"2026-06-24T12:00:00Z\"},{\"id\":1,\"sku\":\"SKU-967340\",\"name\":\"sierra kilo\",\"status\":\"pending\",\"score\":37.31,\"quantity\":46,\"active\":false,\"updatedAt\":\"2026-02-04T12:00:00Z\"},{\"id\":2,\"sku\":\"SKU-334172\",\"name\":\"mike golf\",\"status\":\"archived\",\"score\":94.56,\"quantity\":499,\"active\":false,\"updatedAt\":\"2026-06-24T12:00:00Z\"},{\"id\":3,\"sku\":\"SKU-708572\",\"name\":\"uniform kilo\",\"status\":\"failed\",\"score\":91.72,\"quantity\":414,\"active\":false,\"updatedAt\":\"2026-01-12T12:00:00Z\"},{\"id\":4,\"sku\":\"SKU-59452\",\"name\":\"alpha sierra\",\"status\":\"active\",\"score\":21.56,\"quantity\":215,\"active\":false,\"updatedAt\":\"2026-06-20T12:00:00Z\"},{\"id\":5,\"sku\":\"SKU-354103\",\"name\":\"zulu hotel\",\"status\":\"pending\",\"score\":59.22,\"quantity\":250,\"active\":true,\"updatedAt\":\"2026-05-02T12:00:00Z\"},{\"id\":6,\"sku\":\"SKU-868744\",\"name\":\"whiskey tango\",\"status\":\"active\",\"score\":46.31,\"quantity\":111,\"active\":true,\"updatedAt\":\"2026-01-05T12:00:00Z\"},{\"id\":7,\"sku\":\"SKU-834024\",\"name\":\"delta delta\",\"status\":\"archived\",\"score\":88.68,\"quantity\":221,\"active\":true,\"updatedAt\":\"2026-06-10T12:00:00Z\"},{\"id\":8,\"sku\":\"SKU-4318\",\"name\":\"alpha lima\",\"status\":\"failed\",\"score\":86.59,\"quantity\":237,\"active\":true,\"updatedAt\":\"2026-05-22T12:00:00Z\"},{\"id\":9,\"sku\":\"SKU-397052\",\"name\":\"bravo quebec\",\"status\":\"active\",\"score\":14.17,\"quantity\":244,\"active\":true,\"updatedAt\":\"2026-01-08T12:00:00Z\"},{\"id\":10,\"sku\":\"SKU-987401\",\"name\":\"november sierra\",\"status\":\"failed\",\"score\":67.5,\"quantity\":212,\"active\":true,\"updatedAt\":\"2026-03-22T12:00:00Z\"},{\"id\":11,\"sku\":\"SKU-399261\",\"name\":\"charlie tango\",\"status\":\"active\",\"score\":12.8,\"quantity\":104,\"active\":false,\"updatedAt\":\"2026-05-01T12:00:00Z\"},{\"id\":12,\"sku\":\"SKU-541132\",\"name\":\"india november\",\"status\":\"active\",\"score\":8.18,\"quantity\":21,\"active\":true,\"updatedAt\":\"2026-02-08T12:00:00Z\"},{\"id\":13,\"sku\":\"SKU-664917\",\"name\":\"november golf\",\"status\":\"pending\",\"score\":39.06,\"quantity\":87,\"active\":true,\"updatedAt\":\"2026-02-22T12:00:00Z\"},{\"id\":14,\"sku\":\"SKU-470178\",\"name\":\"papa uniform\",\"status\":\"failed\",\"score\":31.51,\"quantity\":379,\"active\":true,\"updatedAt\":\"2026-04-28T12:00:00Z\"},{\"id\":15,\"sku\":\"SKU-795808\",\"name\":\"papa india\",\"status\":\"active\",\"score\":81.8,\"quantity\":443,\"active\":true,\"updatedAt\":\"2026-02-17T12:00:00Z\"},{\"id\":16,\"sku\":\"SKU-501650\",\"name\":\"golf india\",\"status\":\"pending\",\"score\":94.3,\"quantity\":462,\"active\":true,\"updatedAt\":\"2026-05-11T12:00:00Z\"},{\"id\":17,\"sku\":\"SKU-562629\",\"name\":\"charlie echo\",\"status\":\"archived\",\"score\":34.87,\"quantity\":280,\"active\":true,\"updatedAt\":\"2026-02-21T12:00:00Z\"},{\"id\":18,\"sku\":\"SKU-32189\",\"name\":\"zulu mike\",\"status\":\"pending\",\"score\":58.38,\"quantity\":372,\"active\":false,\"updatedAt\":\"2026-05-13T12:00:00Z\"},{\"id\":19,\"sku\":\"SKU-197718\",\"name\":\"india kilo\",\"status\":\"active\",\"score\":19.81,\"quantity\":296,\"active\":true,\"updatedAt\":\"2026-03-03T12:00:00Z\"},{\"id\":20,\"sku\":\"SKU-770813\",\"name\":\"whiskey delta\",\"status\":\"archived\",\"score\":14.07,\"quantity\":375,\"active\":false,\"updatedAt\":\"2026-06-11T12:00:00Z\"},{\"id\":21,\"sku\":\"SKU-993297\",\"name\":\"lima oscar\",\"status\":\"pending\",\"score\":49.46,\"quantity\":320,\"active\":true,\"updatedAt\":\"2026-06-04T12:00:00Z\"},{\"id\":22,\"sku\":\"SKU-129005\",\"name\":\"india delta\",\"status\":\"active\",\"score\":38.89,\"quantity\":1,\"active\":true,\"updatedAt\":\"2026-06-18T12:00:00Z\"},{\"id\":23,\"sku\":\"SKU-983730\",\"name\":\"bravo papa\",\"status\":\"pending\",\"score\":88.65,\"quantity\":367,\"active\":false,\"updatedAt\":\"2026-01-28T12:00:00Z\"},{\"id\":24,\"sku\":\"SKU-758767\",\"name\":\"romeo tango\",\"status\":\"pending\",\"score\":92.43,\"quantity\":327,\"active\":false,\"updatedAt\":\"2026-04-21T12:00:00Z\"},{\"id\":25,\"sku\":\"SKU-642530\",\"name\":\"charlie whiskey\",\"status\":\"failed\",\"score\":91.16,\"quantity\":139,\"active\":true,\"updatedAt\":\"2026-06-13T12:00:00Z\"},{\"id\":26,\"sku\":\"SKU-465260\",\"name\":\"kilo lima\",\"status\":\"archived\",\"score\":35.1,\"quantity\":257,\"active\":true,\"updatedAt\":\"2026-01-16T12:00:00Z\"},{\"id\":27,\"sku\":\"SKU-73648\",\"name\":\"india mike\",\"status\":\"archived\",\"score\":97.62,\"quantity\":386,\"active\":true,\"updatedAt\":\"2026-05-13T12:00:00Z\"},{\"id\":28,\"sku\":\"SKU-776786\",\"name\":\"victor lima\",\"status\":\"active\",\"score\":27.69,\"quantity\":438,\"active\":true,\"updatedAt\":\"2026-05-27T12:00:00Z\"},{\"id\":29,\"sku\":\"SKU-468983\",\"name\":\"hotel kilo\",\"status\":\"archived\",\"score\":0.48,\"quantity\":239,\"active\":true,\"updatedAt\":\"2026-02-09T12:00:00Z\"},{\"id\":30,\"sku\":\"SKU-563299\",\"name\":\"charlie november\",\"status\":\"archived\",\"score\":13.56,\"quantity\":88,\"active\":true,\"updatedAt\":\"2026-05-08T12:00:00Z\"},{\"id\":31,\"sku\":\"SKU-419442\",\"name\":\"romeo charlie\",\"status\":\"pending\",\"score\":26.86,\"quantity\":346,\"active\":true,\"updatedAt\":\"2026-04-15T12:00:00Z\"},{\"id\":32,\"sku\":\"SKU-182808\",\"name\":\"november tango\",\"status\":\"pending\",\"score\":95.74,\"quantity\":288,\"active\":false,\"updatedAt\":\"2026-04-25T12:00:00Z\"},{\"id\":33,\"sku\":\"SKU-921862\",\"name\":\"zulu sierra\",\"status\":\"active\",\"score\":38.59,\"quantity\":370,\"active\":true,\"updatedAt\":\"2026-02-02T12:00:00Z\"},{\"id\":34,\"sku\":\"SKU-809989\",\"name\":\"golf echo\",\"status\":\"archived\",\"score\":13.09,\"quantity\":427,\"active\":false,\"updatedAt\":\"2026-04-23T12:00:00Z\"},{\"id\":35,\"sku\":\"SKU-684809\",\"name\":\"romeo foxtrot\",\"status\":\"pending\",\"score\":82.4,\"quantity\":444,\"active\":false,\"updatedAt\":\"2026-03-19T12:00:00Z\"},{\"id\":36,\"sku\":\"SKU-56758\",\"name\":\"november victor\",\"status\":\"pending\",\"score\":57.61,\"quantity\":122,\"active\":true,\"updatedAt\":\"2026-02-06T12:00:00Z\"},{\"id\":37,\"sku\":\"SKU-158771\",\"name\":\"hotel quebec\",\"status\":\"archived\",\"score\":52.21,\"quantity\":108,\"active\":true,\"updatedAt\":\"2026-05-13T12:00:00Z\"},{\"id\":38,\"sku\":\"SKU-201657\",\"name\":\"india mike\",\"status\":\"archived\",\"score\":11.98,\"quantity\":279,\"active\":true,\"updatedAt\":\"2026-04-17T12:00:00Z\"},{\"id\":39,\"sku\":\"SKU-934265\",\"name\":\"oscar victor\",\"status\":\"failed\",\"score\":10.39,\"quantity\":10,\"active\":true,\"updatedAt\":\"2026-04-09T12:00:00Z\"},{\"id\":40,\"sku\":\"SKU-980470\",\"name\":\"delta zulu\",\"status\":\"failed\",\"score\":95.34,\"quantity\":73,\"active\":false,\"updatedAt\":\"2026-03-22T12:00:00Z\"},{\"id\":41,\"sku\":\"SKU-522215\",\"name\":\"november lima\",\"status\":\"archived\",\"score\":64.84,\"quantity\":369,\"active\":true,\"updatedAt\":\"2026-05-16T12:00:00Z\"},{\"id\":42,\"sku\":\"SKU-409943\",\"name\":\"hotel zulu\",\"status\":\"archived\",\"score\":11.64,\"quantity\":426,\"active\":true,\"updatedAt\":\"2026-02-11T12:00:00Z\"},{\"id\":43,\"sku\":\"SKU-77806\",\"name\":\"mike lima\",\"status\":\"active\",\"score\":63.83,\"quantity\":203,\"active\":true,\"updatedAt\":\"2026-03-19T12:00:00Z\"},{\"id\":44,\"sku\":\"SKU-928201\",\"name\":\"charlie mike\",\"status\":\"pending\",\"score\":5.06,\"quantity\":450,\"active\":false,\"updatedAt\":\"2026-05-06T12:00:00Z\"},{\"id\":45,\"sku\":\"SKU-619926\",\"name\":\"bravo whiskey\",\"status\":\"pending\",\"score\":20.01,\"quantity\":155,\"active\":true,\"updatedAt\":\"2026-02-06T12:00:00Z\"},{\"id\":46,\"sku\":\"SKU-22057\",\"name\":\"charlie sierra\",\"status\":\"failed\",\"score\":33.47,\"quantity\":435,\"active\":true,\"updatedAt\":\"2026-05-07T12:00:00Z\"},{\"id\":47,\"sku\":\"SKU-638993\",\"name\":\"alpha charlie\",\"status\":\"archived\",\"score\":26.92,\"quantity\":104,\"active\":false,\"updatedAt\":\"2026-05-07T12:00:00Z\"},{\"id\":48,\"sku\":\"SKU-765547\",\"name\":\"echo oscar\",\"status\":\"pending\",\"score\":72.91,\"quantity\":279,\"active\":true,\"updatedAt\":\"2026-02-13T12:00:00Z\"},{\"id\":49,\"sku\":\"SKU-440455\",\"name\":\"echo uniform\",\"status\":\"archived\",\"score\":74.63,\"quantity\":272,\"active\":false,\"updatedAt\":\"2026-06-22T12:00:00Z\"},{\"id\":50,\"sku\":\"SKU-303645\",\"name\":\"papa sierra\",\"status\":\"pending\",\"score\":28.63,\"quantity\":90,\"active\":false,\"updatedAt\":\"2026-06-25T12:00:00Z\"},{\"id\":51,\"sku\":\"SKU-213454\",\"name\":\"november oscar\",\"status\":\"pending\",\"score\":35.5,\"quantity\":159,\"active\":true,\"updatedAt\":\"2026-04-26T12:00:00Z\"},{\"id\":52,\"sku\":\"SKU-715886\",\"name\":\"mike oscar\",\"status\":\"active\",\"score\":73.01,\"quantity\":399,\"active\":true,\"updatedAt\":\"2026-05-21T12:00:00Z\"},{\"id\":53,\"sku\":\"SKU-586691\",\"name\":\"delta zulu\",\"status\":\"archived\",\"score\":48.69,\"quantity\":373,\"active\":false,\"updatedAt\":\"2026-02-10T12:00:00Z\"},{\"id\":54,\"sku\":\"SKU-236865\",\"name\":\"oscar romeo\",\"status\":\"archived\",\"score\":66.72,\"quantity\":241,\"active\":false,\"updatedAt\":\"2026-01-07T12:00:00Z\"},{\"id\":55,\"sku\":\"SKU-157251\",\"name\":\"alpha delta\",\"status\":\"archived\",\"score\":20.83,\"quantity\":58,\"active\":true,\"updatedAt\":\"2026-04-16T12:00:00Z\"},{\"id\":56,\"sku\":\"SKU-262253\",\"name\":\"golf echo\",\"status\":\"failed\",\"score\":33.33,\"quantity\":168,\"active\":false,\"updatedAt\":\"2026-03-12T12:00:00Z\"},{\"id\":57,\"sku\":\"SKU-863890\",\"name\":\"victor whiskey\",\"status\":\"archived\",\"score\":59.55,\"quantity\":199,\"active\":true,\"updatedAt\":\"2026-02-12T12:00:00Z\"},{\"id\":58,\"sku\":\"SKU-602861\",\"name\":\"bravo romeo\",\"status\":\"archived\",\"score\":62.56,\"quantity\":107,\"active\":false,\"updatedAt\":\"2026-02-14T12:00:00Z\"},{\"id\":59,\"sku\":\"SKU-140459\",\"name\":\"tango oscar\",\"status\":\"pending\",\"score\":6.22,\"quantity\":52,\"active\":false,\"updatedAt\":\"2026-01-21T12:00:00Z\"},{\"id\":60,\"sku\":\"SKU-736207\",\"name\":\"victor delta\",\"status\":\"active\",\"score\":44.62,\"quantity\":24,\"active\":true,\"updatedAt\":\"2026-03-13T12:00:00Z\"},{\"id\":61,\"sku\":\"SKU-759724\",\"name\":\"delta juliet\",\"status\":\"pending\",\"score\":26.46,\"quantity\":384,\"active\":false,\"updatedAt\":\"2026-05-19T12:00:00Z\"},{\"id\":62,\"sku\":\"SKU-442602\",\"name\":\"alpha golf\",\"status\":\"archived\",\"score\":49.58,\"quantity\":228,\"active\":true,\"updatedAt\":\"2026-05-05T12:00:00Z\"},{\"id\":63,\"sku\":\"SKU-575268\",\"name\":\"romeo foxtrot\",\"status\":\"archived\",\"score\":14.2,\"quantity\":267,\"active\":true,\"updatedAt\":\"2026-05-05T12:00:00Z\"},{\"id\":64,\"sku\":\"SKU-648994\",\"name\":\"hotel romeo\",\"status\":\"archived\",\"score\":44.89,\"quantity\":321,\"active\":false,\"updatedAt\":\"2026-03-11T12:00:00Z\"},{\"id\":65,\"sku\":\"SKU-188457\",\"name\":\"mike echo\",\"status\":\"active\",\"score\":94.79,\"quantity\":432,\"active\":false,\"updatedAt\":\"2026-05-21T12:00:00Z\"},{\"id\":66,\"sku\":\"SKU-149621\",\"name\":\"golf november\",\"status\":\"pending\",\"score\":90.25,\"quantity\":465,\"active\":false,\"updatedAt\":\"2026-03-11T12:00:00Z\"},{\"id\":67,\"sku\":\"SKU-285740\",\"name\":\"romeo charlie\",\"status\":\"pending\",\"score\":93.89,\"quantity\":5,\"active\":false,\"updatedAt\":\"2026-04-11T12:00:00Z\"},{\"id\":68,\"sku\":\"SKU-291992\",\"name\":\"sierra lima\",\"status\":\"active\",\"score\":86.27,\"quantity\":85,\"active\":false,\"updatedAt\":\"2026-02-07T12:00:00Z\"},{\"id\":69,\"sku\":\"SKU-747919\",\"name\":\"golf whiskey\",\"status\":\"failed\",\"score\":90.75,\"quantity\":226,\"active\":false,\"updatedAt\":\"2026-04-09T12:00:00Z\"}]", + "encoded": "[70]{id,sku,name,status,score,quantity,active,updatedAt}:\n 0,\"SKU-369981\",lima oscar,failed,9.74,27,false,\"2026-06-24T12:00:00Z\"\n 1,\"SKU-967340\",sierra kilo,pending,37.31,46,false,\"2026-02-04T12:00:00Z\"\n 2,\"SKU-334172\",mike golf,archived,94.56,499,false,\"2026-06-24T12:00:00Z\"\n 3,\"SKU-708572\",uniform kilo,failed,91.72,414,false,\"2026-01-12T12:00:00Z\"\n 4,\"SKU-59452\",alpha sierra,active,21.56,215,false,\"2026-06-20T12:00:00Z\"\n 5,\"SKU-354103\",zulu hotel,pending,59.22,250,true,\"2026-05-02T12:00:00Z\"\n 6,\"SKU-868744\",whiskey tango,active,46.31,111,true,\"2026-01-05T12:00:00Z\"\n 7,\"SKU-834024\",delta delta,archived,88.68,221,true,\"2026-06-10T12:00:00Z\"\n 8,\"SKU-4318\",alpha lima,failed,86.59,237,true,\"2026-05-22T12:00:00Z\"\n 9,\"SKU-397052\",bravo quebec,active,14.17,244,true,\"2026-01-08T12:00:00Z\"\n 10,\"SKU-987401\",november sierra,failed,67.5,212,true,\"2026-03-22T12:00:00Z\"\n 11,\"SKU-399261\",charlie tango,active,12.8,104,false,\"2026-05-01T12:00:00Z\"\n 12,\"SKU-541132\",india november,active,8.18,21,true,\"2026-02-08T12:00:00Z\"\n 13,\"SKU-664917\",november golf,pending,39.06,87,true,\"2026-02-22T12:00:00Z\"\n 14,\"SKU-470178\",papa uniform,failed,31.51,379,true,\"2026-04-28T12:00:00Z\"\n 15,\"SKU-795808\",papa india,active,81.8,443,true,\"2026-02-17T12:00:00Z\"\n 16,\"SKU-501650\",golf india,pending,94.3,462,true,\"2026-05-11T12:00:00Z\"\n 17,\"SKU-562629\",charlie echo,archived,34.87,280,true,\"2026-02-21T12:00:00Z\"\n 18,\"SKU-32189\",zulu mike,pending,58.38,372,false,\"2026-05-13T12:00:00Z\"\n 19,\"SKU-197718\",india kilo,active,19.81,296,true,\"2026-03-03T12:00:00Z\"\n 20,\"SKU-770813\",whiskey delta,archived,14.07,375,false,\"2026-06-11T12:00:00Z\"\n 21,\"SKU-993297\",lima oscar,pending,49.46,320,true,\"2026-06-04T12:00:00Z\"\n 22,\"SKU-129005\",india delta,active,38.89,1,true,\"2026-06-18T12:00:00Z\"\n 23,\"SKU-983730\",bravo papa,pending,88.65,367,false,\"2026-01-28T12:00:00Z\"\n 24,\"SKU-758767\",romeo tango,pending,92.43,327,false,\"2026-04-21T12:00:00Z\"\n 25,\"SKU-642530\",charlie whiskey,failed,91.16,139,true,\"2026-06-13T12:00:00Z\"\n 26,\"SKU-465260\",kilo lima,archived,35.1,257,true,\"2026-01-16T12:00:00Z\"\n 27,\"SKU-73648\",india mike,archived,97.62,386,true,\"2026-05-13T12:00:00Z\"\n 28,\"SKU-776786\",victor lima,active,27.69,438,true,\"2026-05-27T12:00:00Z\"\n 29,\"SKU-468983\",hotel kilo,archived,0.48,239,true,\"2026-02-09T12:00:00Z\"\n 30,\"SKU-563299\",charlie november,archived,13.56,88,true,\"2026-05-08T12:00:00Z\"\n 31,\"SKU-419442\",romeo charlie,pending,26.86,346,true,\"2026-04-15T12:00:00Z\"\n 32,\"SKU-182808\",november tango,pending,95.74,288,false,\"2026-04-25T12:00:00Z\"\n 33,\"SKU-921862\",zulu sierra,active,38.59,370,true,\"2026-02-02T12:00:00Z\"\n 34,\"SKU-809989\",golf echo,archived,13.09,427,false,\"2026-04-23T12:00:00Z\"\n 35,\"SKU-684809\",romeo foxtrot,pending,82.4,444,false,\"2026-03-19T12:00:00Z\"\n 36,\"SKU-56758\",november victor,pending,57.61,122,true,\"2026-02-06T12:00:00Z\"\n 37,\"SKU-158771\",hotel quebec,archived,52.21,108,true,\"2026-05-13T12:00:00Z\"\n 38,\"SKU-201657\",india mike,archived,11.98,279,true,\"2026-04-17T12:00:00Z\"\n 39,\"SKU-934265\",oscar victor,failed,10.39,10,true,\"2026-04-09T12:00:00Z\"\n 40,\"SKU-980470\",delta zulu,failed,95.34,73,false,\"2026-03-22T12:00:00Z\"\n 41,\"SKU-522215\",november lima,archived,64.84,369,true,\"2026-05-16T12:00:00Z\"\n 42,\"SKU-409943\",hotel zulu,archived,11.64,426,true,\"2026-02-11T12:00:00Z\"\n 43,\"SKU-77806\",mike lima,active,63.83,203,true,\"2026-03-19T12:00:00Z\"\n 44,\"SKU-928201\",charlie mike,pending,5.06,450,false,\"2026-05-06T12:00:00Z\"\n 45,\"SKU-619926\",bravo whiskey,pending,20.01,155,true,\"2026-02-06T12:00:00Z\"\n 46,\"SKU-22057\",charlie sierra,failed,33.47,435,true,\"2026-05-07T12:00:00Z\"\n 47,\"SKU-638993\",alpha charlie,archived,26.92,104,false,\"2026-05-07T12:00:00Z\"\n 48,\"SKU-765547\",echo oscar,pending,72.91,279,true,\"2026-02-13T12:00:00Z\"\n 49,\"SKU-440455\",echo uniform,archived,74.63,272,false,\"2026-06-22T12:00:00Z\"\n 50,\"SKU-303645\",papa sierra,pending,28.63,90,false,\"2026-06-25T12:00:00Z\"\n 51,\"SKU-213454\",november oscar,pending,35.5,159,true,\"2026-04-26T12:00:00Z\"\n 52,\"SKU-715886\",mike oscar,active,73.01,399,true,\"2026-05-21T12:00:00Z\"\n 53,\"SKU-586691\",delta zulu,archived,48.69,373,false,\"2026-02-10T12:00:00Z\"\n 54,\"SKU-236865\",oscar romeo,archived,66.72,241,false,\"2026-01-07T12:00:00Z\"\n 55,\"SKU-157251\",alpha delta,archived,20.83,58,true,\"2026-04-16T12:00:00Z\"\n 56,\"SKU-262253\",golf echo,failed,33.33,168,false,\"2026-03-12T12:00:00Z\"\n 57,\"SKU-863890\",victor whiskey,archived,59.55,199,true,\"2026-02-12T12:00:00Z\"\n 58,\"SKU-602861\",bravo romeo,archived,62.56,107,false,\"2026-02-14T12:00:00Z\"\n 59,\"SKU-140459\",tango oscar,pending,6.22,52,false,\"2026-01-21T12:00:00Z\"\n 60,\"SKU-736207\",victor delta,active,44.62,24,true,\"2026-03-13T12:00:00Z\"\n 61,\"SKU-759724\",delta juliet,pending,26.46,384,false,\"2026-05-19T12:00:00Z\"\n 62,\"SKU-442602\",alpha golf,archived,49.58,228,true,\"2026-05-05T12:00:00Z\"\n 63,\"SKU-575268\",romeo foxtrot,archived,14.2,267,true,\"2026-05-05T12:00:00Z\"\n 64,\"SKU-648994\",hotel romeo,archived,44.89,321,false,\"2026-03-11T12:00:00Z\"\n 65,\"SKU-188457\",mike echo,active,94.79,432,false,\"2026-05-21T12:00:00Z\"\n 66,\"SKU-149621\",golf november,pending,90.25,465,false,\"2026-03-11T12:00:00Z\"\n 67,\"SKU-285740\",romeo charlie,pending,93.89,5,false,\"2026-04-11T12:00:00Z\"\n 68,\"SKU-291992\",sierra lima,active,86.27,85,false,\"2026-02-07T12:00:00Z\"\n 69,\"SKU-747919\",golf whiskey,failed,90.75,226,false,\"2026-04-09T12:00:00Z\"" + } + }, + { + "name": "bench-10kb-2", + "rawContent": "{\"meta\":{\"source\":\"bench\",\"version\":3,\"total\":50},\"entries\":{\"entry_0\":{\"id\":0,\"sku\":\"SKU-509743\",\"name\":\"zulu charlie\",\"status\":\"archived\",\"score\":87.86,\"quantity\":46,\"active\":true,\"updatedAt\":\"2026-02-04T12:00:00Z\",\"nested\":{\"tags\":[\"november\",\"tango\"],\"depth\":2}},\"entry_1\":{\"id\":1,\"sku\":\"SKU-409669\",\"name\":\"quebec zulu\",\"status\":\"pending\",\"score\":9.37,\"quantity\":361,\"active\":false,\"updatedAt\":\"2026-06-27T12:00:00Z\",\"nested\":{\"tags\":[\"golf\",\"victor\"],\"depth\":2}},\"entry_2\":{\"id\":2,\"sku\":\"SKU-564643\",\"name\":\"romeo zulu\",\"status\":\"pending\",\"score\":5.29,\"quantity\":342,\"active\":true,\"updatedAt\":\"2026-03-28T12:00:00Z\",\"nested\":{\"tags\":[\"echo\",\"alpha\"],\"depth\":2}},\"entry_3\":{\"id\":3,\"sku\":\"SKU-49672\",\"name\":\"delta romeo\",\"status\":\"pending\",\"score\":58.97,\"quantity\":72,\"active\":true,\"updatedAt\":\"2026-05-03T12:00:00Z\",\"nested\":{\"tags\":[\"delta\",\"romeo\"],\"depth\":2}},\"entry_4\":{\"id\":4,\"sku\":\"SKU-461045\",\"name\":\"oscar echo\",\"status\":\"failed\",\"score\":84.05,\"quantity\":44,\"active\":true,\"updatedAt\":\"2026-05-01T12:00:00Z\",\"nested\":{\"tags\":[\"whiskey\",\"romeo\"],\"depth\":2}},\"entry_5\":{\"id\":5,\"sku\":\"SKU-434641\",\"name\":\"alpha foxtrot\",\"status\":\"pending\",\"score\":21.77,\"quantity\":262,\"active\":false,\"updatedAt\":\"2026-04-17T12:00:00Z\",\"nested\":{\"tags\":[\"hotel\",\"mike\"],\"depth\":2}},\"entry_6\":{\"id\":6,\"sku\":\"SKU-575426\",\"name\":\"victor golf\",\"status\":\"archived\",\"score\":88.95,\"quantity\":81,\"active\":true,\"updatedAt\":\"2026-01-25T12:00:00Z\",\"nested\":{\"tags\":[\"juliet\",\"alpha\"],\"depth\":2}},\"entry_7\":{\"id\":7,\"sku\":\"SKU-334021\",\"name\":\"romeo oscar\",\"status\":\"failed\",\"score\":16.58,\"quantity\":67,\"active\":false,\"updatedAt\":\"2026-05-25T12:00:00Z\",\"nested\":{\"tags\":[\"juliet\",\"whiskey\"],\"depth\":2}},\"entry_8\":{\"id\":8,\"sku\":\"SKU-540445\",\"name\":\"delta hotel\",\"status\":\"pending\",\"score\":53.51,\"quantity\":266,\"active\":true,\"updatedAt\":\"2026-03-16T12:00:00Z\",\"nested\":{\"tags\":[\"victor\",\"sierra\"],\"depth\":2}},\"entry_9\":{\"id\":9,\"sku\":\"SKU-256829\",\"name\":\"alpha zulu\",\"status\":\"pending\",\"score\":9.35,\"quantity\":292,\"active\":true,\"updatedAt\":\"2026-02-10T12:00:00Z\",\"nested\":{\"tags\":[\"romeo\",\"alpha\"],\"depth\":2}},\"entry_10\":{\"id\":10,\"sku\":\"SKU-950846\",\"name\":\"bravo foxtrot\",\"status\":\"active\",\"score\":49.13,\"quantity\":26,\"active\":true,\"updatedAt\":\"2026-01-04T12:00:00Z\",\"nested\":{\"tags\":[\"delta\",\"india\"],\"depth\":2}},\"entry_11\":{\"id\":11,\"sku\":\"SKU-617755\",\"name\":\"mike zulu\",\"status\":\"pending\",\"score\":4.97,\"quantity\":415,\"active\":false,\"updatedAt\":\"2026-01-17T12:00:00Z\",\"nested\":{\"tags\":[\"mike\",\"echo\"],\"depth\":2}},\"entry_12\":{\"id\":12,\"sku\":\"SKU-581445\",\"name\":\"lima victor\",\"status\":\"archived\",\"score\":59.99,\"quantity\":485,\"active\":false,\"updatedAt\":\"2026-05-05T12:00:00Z\",\"nested\":{\"tags\":[\"alpha\",\"lima\"],\"depth\":2}},\"entry_13\":{\"id\":13,\"sku\":\"SKU-719044\",\"name\":\"oscar kilo\",\"status\":\"active\",\"score\":63.28,\"quantity\":24,\"active\":false,\"updatedAt\":\"2026-05-28T12:00:00Z\",\"nested\":{\"tags\":[\"hotel\",\"mike\"],\"depth\":2}},\"entry_14\":{\"id\":14,\"sku\":\"SKU-789713\",\"name\":\"papa oscar\",\"status\":\"archived\",\"score\":27.49,\"quantity\":198,\"active\":false,\"updatedAt\":\"2026-04-15T12:00:00Z\",\"nested\":{\"tags\":[\"juliet\",\"alpha\"],\"depth\":2}},\"entry_15\":{\"id\":15,\"sku\":\"SKU-534443\",\"name\":\"delta india\",\"status\":\"archived\",\"score\":62.51,\"quantity\":361,\"active\":false,\"updatedAt\":\"2026-05-12T12:00:00Z\",\"nested\":{\"tags\":[\"golf\",\"lima\"],\"depth\":2}},\"entry_16\":{\"id\":16,\"sku\":\"SKU-894299\",\"name\":\"delta golf\",\"status\":\"active\",\"score\":4.53,\"quantity\":321,\"active\":false,\"updatedAt\":\"2026-06-06T12:00:00Z\",\"nested\":{\"tags\":[\"uniform\",\"zulu\"],\"depth\":2}},\"entry_17\":{\"id\":17,\"sku\":\"SKU-178180\",\"name\":\"hotel oscar\",\"status\":\"active\",\"score\":94.67,\"quantity\":438,\"active\":false,\"updatedAt\":\"2026-04-12T12:00:00Z\",\"nested\":{\"tags\":[\"romeo\",\"quebec\"],\"depth\":2}},\"entry_18\":{\"id\":18,\"sku\":\"SKU-474295\",\"name\":\"november juliet\",\"status\":\"archived\",\"score\":52.97,\"quantity\":162,\"active\":true,\"updatedAt\":\"2026-06-12T12:00:00Z\",\"nested\":{\"tags\":[\"foxtrot\",\"romeo\"],\"depth\":2}},\"entry_19\":{\"id\":19,\"sku\":\"SKU-526106\",\"name\":\"golf romeo\",\"status\":\"archived\",\"score\":51.51,\"quantity\":176,\"active\":true,\"updatedAt\":\"2026-01-14T12:00:00Z\",\"nested\":{\"tags\":[\"tango\",\"quebec\"],\"depth\":2}},\"entry_20\":{\"id\":20,\"sku\":\"SKU-643873\",\"name\":\"foxtrot tango\",\"status\":\"pending\",\"score\":84.13,\"quantity\":321,\"active\":false,\"updatedAt\":\"2026-05-05T12:00:00Z\",\"nested\":{\"tags\":[\"tango\",\"uniform\"],\"depth\":2}},\"entry_21\":{\"id\":21,\"sku\":\"SKU-881747\",\"name\":\"india charlie\",\"status\":\"active\",\"score\":99.94,\"quantity\":215,\"active\":false,\"updatedAt\":\"2026-03-09T12:00:00Z\",\"nested\":{\"tags\":[\"india\",\"delta\"],\"depth\":2}},\"entry_22\":{\"id\":22,\"sku\":\"SKU-536249\",\"name\":\"oscar echo\",\"status\":\"pending\",\"score\":49.7,\"quantity\":198,\"active\":true,\"updatedAt\":\"2026-02-23T12:00:00Z\",\"nested\":{\"tags\":[\"quebec\",\"juliet\"],\"depth\":2}},\"entry_23\":{\"id\":23,\"sku\":\"SKU-242438\",\"name\":\"zulu alpha\",\"status\":\"failed\",\"score\":15.35,\"quantity\":282,\"active\":false,\"updatedAt\":\"2026-01-11T12:00:00Z\",\"nested\":{\"tags\":[\"november\",\"kilo\"],\"depth\":2}},\"entry_24\":{\"id\":24,\"sku\":\"SKU-787964\",\"name\":\"oscar hotel\",\"status\":\"archived\",\"score\":35.78,\"quantity\":383,\"active\":true,\"updatedAt\":\"2026-04-21T12:00:00Z\",\"nested\":{\"tags\":[\"zulu\",\"uniform\"],\"depth\":2}},\"entry_25\":{\"id\":25,\"sku\":\"SKU-270620\",\"name\":\"india india\",\"status\":\"pending\",\"score\":30.89,\"quantity\":367,\"active\":false,\"updatedAt\":\"2026-04-10T12:00:00Z\",\"nested\":{\"tags\":[\"tango\",\"echo\"],\"depth\":2}},\"entry_26\":{\"id\":26,\"sku\":\"SKU-479773\",\"name\":\"whiskey papa\",\"status\":\"archived\",\"score\":90.09,\"quantity\":10,\"active\":false,\"updatedAt\":\"2026-06-16T12:00:00Z\",\"nested\":{\"tags\":[\"uniform\",\"foxtrot\"],\"depth\":2}},\"entry_27\":{\"id\":27,\"sku\":\"SKU-85068\",\"name\":\"kilo zulu\",\"status\":\"archived\",\"score\":11.57,\"quantity\":100,\"active\":true,\"updatedAt\":\"2026-04-01T12:00:00Z\",\"nested\":{\"tags\":[\"uniform\",\"juliet\"],\"depth\":2}},\"entry_28\":{\"id\":28,\"sku\":\"SKU-88619\",\"name\":\"alpha echo\",\"status\":\"pending\",\"score\":86.22,\"quantity\":23,\"active\":true,\"updatedAt\":\"2026-03-02T12:00:00Z\",\"nested\":{\"tags\":[\"romeo\",\"victor\"],\"depth\":2}},\"entry_29\":{\"id\":29,\"sku\":\"SKU-342557\",\"name\":\"bravo papa\",\"status\":\"failed\",\"score\":57.84,\"quantity\":493,\"active\":false,\"updatedAt\":\"2026-05-15T12:00:00Z\",\"nested\":{\"tags\":[\"echo\",\"foxtrot\"],\"depth\":2}},\"entry_30\":{\"id\":30,\"sku\":\"SKU-64537\",\"name\":\"quebec charlie\",\"status\":\"failed\",\"score\":65.9,\"quantity\":334,\"active\":false,\"updatedAt\":\"2026-04-15T12:00:00Z\",\"nested\":{\"tags\":[\"quebec\",\"juliet\"],\"depth\":2}},\"entry_31\":{\"id\":31,\"sku\":\"SKU-45365\",\"name\":\"papa golf\",\"status\":\"pending\",\"score\":56.24,\"quantity\":315,\"active\":false,\"updatedAt\":\"2026-05-07T12:00:00Z\",\"nested\":{\"tags\":[\"bravo\",\"charlie\"],\"depth\":2}},\"entry_32\":{\"id\":32,\"sku\":\"SKU-306983\",\"name\":\"quebec foxtrot\",\"status\":\"active\",\"score\":6.96,\"quantity\":276,\"active\":true,\"updatedAt\":\"2026-02-17T12:00:00Z\",\"nested\":{\"tags\":[\"romeo\",\"victor\"],\"depth\":2}},\"entry_33\":{\"id\":33,\"sku\":\"SKU-100839\",\"name\":\"alpha lima\",\"status\":\"active\",\"score\":7.02,\"quantity\":308,\"active\":true,\"updatedAt\":\"2026-03-01T12:00:00Z\",\"nested\":{\"tags\":[\"uniform\",\"victor\"],\"depth\":2}},\"entry_34\":{\"id\":34,\"sku\":\"SKU-285603\",\"name\":\"quebec echo\",\"status\":\"pending\",\"score\":54.61,\"quantity\":276,\"active\":false,\"updatedAt\":\"2026-04-12T12:00:00Z\",\"nested\":{\"tags\":[\"echo\",\"kilo\"],\"depth\":2}},\"entry_35\":{\"id\":35,\"sku\":\"SKU-129333\",\"name\":\"foxtrot delta\",\"status\":\"pending\",\"score\":0.69,\"quantity\":36,\"active\":true,\"updatedAt\":\"2026-02-11T12:00:00Z\",\"nested\":{\"tags\":[\"november\",\"sierra\"],\"depth\":2}},\"entry_36\":{\"id\":36,\"sku\":\"SKU-704370\",\"name\":\"sierra oscar\",\"status\":\"active\",\"score\":45.65,\"quantity\":239,\"active\":false,\"updatedAt\":\"2026-01-22T12:00:00Z\",\"nested\":{\"tags\":[\"kilo\",\"oscar\"],\"depth\":2}},\"entry_37\":{\"id\":37,\"sku\":\"SKU-810963\",\"name\":\"foxtrot alpha\",\"status\":\"failed\",\"score\":59.48,\"quantity\":48,\"active\":true,\"updatedAt\":\"2026-06-24T12:00:00Z\",\"nested\":{\"tags\":[\"juliet\",\"zulu\"],\"depth\":2}},\"entry_38\":{\"id\":38,\"sku\":\"SKU-171741\",\"name\":\"zulu uniform\",\"status\":\"failed\",\"score\":42.96,\"quantity\":48,\"active\":true,\"updatedAt\":\"2026-03-22T12:00:00Z\",\"nested\":{\"tags\":[\"delta\",\"charlie\"],\"depth\":2}},\"entry_39\":{\"id\":39,\"sku\":\"SKU-893779\",\"name\":\"alpha tango\",\"status\":\"archived\",\"score\":0.81,\"quantity\":390,\"active\":false,\"updatedAt\":\"2026-06-25T12:00:00Z\",\"nested\":{\"tags\":[\"echo\",\"victor\"],\"depth\":2}},\"entry_40\":{\"id\":40,\"sku\":\"SKU-455048\",\"name\":\"quebec uniform\",\"status\":\"archived\",\"score\":52.81,\"quantity\":296,\"active\":true,\"updatedAt\":\"2026-03-11T12:00:00Z\",\"nested\":{\"tags\":[\"quebec\",\"kilo\"],\"depth\":2}},\"entry_41\":{\"id\":41,\"sku\":\"SKU-579597\",\"name\":\"alpha bravo\",\"status\":\"active\",\"score\":74.63,\"quantity\":209,\"active\":false,\"updatedAt\":\"2026-02-05T12:00:00Z\",\"nested\":{\"tags\":[\"papa\",\"charlie\"],\"depth\":2}},\"entry_42\":{\"id\":42,\"sku\":\"SKU-927995\",\"name\":\"bravo oscar\",\"status\":\"failed\",\"score\":46.03,\"quantity\":13,\"active\":true,\"updatedAt\":\"2026-01-18T12:00:00Z\",\"nested\":{\"tags\":[\"romeo\",\"uniform\"],\"depth\":2}},\"entry_43\":{\"id\":43,\"sku\":\"SKU-640313\",\"name\":\"oscar foxtrot\",\"status\":\"failed\",\"score\":75.06,\"quantity\":102,\"active\":true,\"updatedAt\":\"2026-06-10T12:00:00Z\",\"nested\":{\"tags\":[\"november\",\"quebec\"],\"depth\":2}},\"entry_44\":{\"id\":44,\"sku\":\"SKU-587339\",\"name\":\"delta lima\",\"status\":\"pending\",\"score\":1.02,\"quantity\":160,\"active\":false,\"updatedAt\":\"2026-03-12T12:00:00Z\",\"nested\":{\"tags\":[\"kilo\",\"november\"],\"depth\":2}},\"entry_45\":{\"id\":45,\"sku\":\"SKU-22977\",\"name\":\"alpha charlie\",\"status\":\"active\",\"score\":91.65,\"quantity\":457,\"active\":false,\"updatedAt\":\"2026-02-26T12:00:00Z\",\"nested\":{\"tags\":[\"india\",\"kilo\"],\"depth\":2}},\"entry_46\":{\"id\":46,\"sku\":\"SKU-274156\",\"name\":\"foxtrot oscar\",\"status\":\"archived\",\"score\":31.37,\"quantity\":424,\"active\":true,\"updatedAt\":\"2026-01-24T12:00:00Z\",\"nested\":{\"tags\":[\"alpha\",\"foxtrot\"],\"depth\":2}},\"entry_47\":{\"id\":47,\"sku\":\"SKU-533189\",\"name\":\"golf lima\",\"status\":\"active\",\"score\":9.71,\"quantity\":251,\"active\":false,\"updatedAt\":\"2026-04-01T12:00:00Z\",\"nested\":{\"tags\":[\"india\",\"kilo\"],\"depth\":2}},\"entry_48\":{\"id\":48,\"sku\":\"SKU-290356\",\"name\":\"zulu zulu\",\"status\":\"archived\",\"score\":25.89,\"quantity\":432,\"active\":false,\"updatedAt\":\"2026-04-14T12:00:00Z\",\"nested\":{\"tags\":[\"alpha\",\"kilo\"],\"depth\":2}},\"entry_49\":{\"id\":49,\"sku\":\"SKU-746759\",\"name\":\"juliet tango\",\"status\":\"active\",\"score\":40.83,\"quantity\":374,\"active\":false,\"updatedAt\":\"2026-06-22T12:00:00Z\",\"nested\":{\"tags\":[\"delta\",\"golf\"],\"depth\":2}}}}", + "unwrap": true, + "expected": { + "normalized": "{\"meta\":{\"source\":\"bench\",\"version\":3,\"total\":50},\"entries\":{\"entry_0\":{\"id\":0,\"sku\":\"SKU-509743\",\"name\":\"zulu charlie\",\"status\":\"archived\",\"score\":87.86,\"quantity\":46,\"active\":true,\"updatedAt\":\"2026-02-04T12:00:00Z\",\"nested\":{\"tags\":[\"november\",\"tango\"],\"depth\":2}},\"entry_1\":{\"id\":1,\"sku\":\"SKU-409669\",\"name\":\"quebec zulu\",\"status\":\"pending\",\"score\":9.37,\"quantity\":361,\"active\":false,\"updatedAt\":\"2026-06-27T12:00:00Z\",\"nested\":{\"tags\":[\"golf\",\"victor\"],\"depth\":2}},\"entry_2\":{\"id\":2,\"sku\":\"SKU-564643\",\"name\":\"romeo zulu\",\"status\":\"pending\",\"score\":5.29,\"quantity\":342,\"active\":true,\"updatedAt\":\"2026-03-28T12:00:00Z\",\"nested\":{\"tags\":[\"echo\",\"alpha\"],\"depth\":2}},\"entry_3\":{\"id\":3,\"sku\":\"SKU-49672\",\"name\":\"delta romeo\",\"status\":\"pending\",\"score\":58.97,\"quantity\":72,\"active\":true,\"updatedAt\":\"2026-05-03T12:00:00Z\",\"nested\":{\"tags\":[\"delta\",\"romeo\"],\"depth\":2}},\"entry_4\":{\"id\":4,\"sku\":\"SKU-461045\",\"name\":\"oscar echo\",\"status\":\"failed\",\"score\":84.05,\"quantity\":44,\"active\":true,\"updatedAt\":\"2026-05-01T12:00:00Z\",\"nested\":{\"tags\":[\"whiskey\",\"romeo\"],\"depth\":2}},\"entry_5\":{\"id\":5,\"sku\":\"SKU-434641\",\"name\":\"alpha foxtrot\",\"status\":\"pending\",\"score\":21.77,\"quantity\":262,\"active\":false,\"updatedAt\":\"2026-04-17T12:00:00Z\",\"nested\":{\"tags\":[\"hotel\",\"mike\"],\"depth\":2}},\"entry_6\":{\"id\":6,\"sku\":\"SKU-575426\",\"name\":\"victor golf\",\"status\":\"archived\",\"score\":88.95,\"quantity\":81,\"active\":true,\"updatedAt\":\"2026-01-25T12:00:00Z\",\"nested\":{\"tags\":[\"juliet\",\"alpha\"],\"depth\":2}},\"entry_7\":{\"id\":7,\"sku\":\"SKU-334021\",\"name\":\"romeo oscar\",\"status\":\"failed\",\"score\":16.58,\"quantity\":67,\"active\":false,\"updatedAt\":\"2026-05-25T12:00:00Z\",\"nested\":{\"tags\":[\"juliet\",\"whiskey\"],\"depth\":2}},\"entry_8\":{\"id\":8,\"sku\":\"SKU-540445\",\"name\":\"delta hotel\",\"status\":\"pending\",\"score\":53.51,\"quantity\":266,\"active\":true,\"updatedAt\":\"2026-03-16T12:00:00Z\",\"nested\":{\"tags\":[\"victor\",\"sierra\"],\"depth\":2}},\"entry_9\":{\"id\":9,\"sku\":\"SKU-256829\",\"name\":\"alpha zulu\",\"status\":\"pending\",\"score\":9.35,\"quantity\":292,\"active\":true,\"updatedAt\":\"2026-02-10T12:00:00Z\",\"nested\":{\"tags\":[\"romeo\",\"alpha\"],\"depth\":2}},\"entry_10\":{\"id\":10,\"sku\":\"SKU-950846\",\"name\":\"bravo foxtrot\",\"status\":\"active\",\"score\":49.13,\"quantity\":26,\"active\":true,\"updatedAt\":\"2026-01-04T12:00:00Z\",\"nested\":{\"tags\":[\"delta\",\"india\"],\"depth\":2}},\"entry_11\":{\"id\":11,\"sku\":\"SKU-617755\",\"name\":\"mike zulu\",\"status\":\"pending\",\"score\":4.97,\"quantity\":415,\"active\":false,\"updatedAt\":\"2026-01-17T12:00:00Z\",\"nested\":{\"tags\":[\"mike\",\"echo\"],\"depth\":2}},\"entry_12\":{\"id\":12,\"sku\":\"SKU-581445\",\"name\":\"lima victor\",\"status\":\"archived\",\"score\":59.99,\"quantity\":485,\"active\":false,\"updatedAt\":\"2026-05-05T12:00:00Z\",\"nested\":{\"tags\":[\"alpha\",\"lima\"],\"depth\":2}},\"entry_13\":{\"id\":13,\"sku\":\"SKU-719044\",\"name\":\"oscar kilo\",\"status\":\"active\",\"score\":63.28,\"quantity\":24,\"active\":false,\"updatedAt\":\"2026-05-28T12:00:00Z\",\"nested\":{\"tags\":[\"hotel\",\"mike\"],\"depth\":2}},\"entry_14\":{\"id\":14,\"sku\":\"SKU-789713\",\"name\":\"papa oscar\",\"status\":\"archived\",\"score\":27.49,\"quantity\":198,\"active\":false,\"updatedAt\":\"2026-04-15T12:00:00Z\",\"nested\":{\"tags\":[\"juliet\",\"alpha\"],\"depth\":2}},\"entry_15\":{\"id\":15,\"sku\":\"SKU-534443\",\"name\":\"delta india\",\"status\":\"archived\",\"score\":62.51,\"quantity\":361,\"active\":false,\"updatedAt\":\"2026-05-12T12:00:00Z\",\"nested\":{\"tags\":[\"golf\",\"lima\"],\"depth\":2}},\"entry_16\":{\"id\":16,\"sku\":\"SKU-894299\",\"name\":\"delta golf\",\"status\":\"active\",\"score\":4.53,\"quantity\":321,\"active\":false,\"updatedAt\":\"2026-06-06T12:00:00Z\",\"nested\":{\"tags\":[\"uniform\",\"zulu\"],\"depth\":2}},\"entry_17\":{\"id\":17,\"sku\":\"SKU-178180\",\"name\":\"hotel oscar\",\"status\":\"active\",\"score\":94.67,\"quantity\":438,\"active\":false,\"updatedAt\":\"2026-04-12T12:00:00Z\",\"nested\":{\"tags\":[\"romeo\",\"quebec\"],\"depth\":2}},\"entry_18\":{\"id\":18,\"sku\":\"SKU-474295\",\"name\":\"november juliet\",\"status\":\"archived\",\"score\":52.97,\"quantity\":162,\"active\":true,\"updatedAt\":\"2026-06-12T12:00:00Z\",\"nested\":{\"tags\":[\"foxtrot\",\"romeo\"],\"depth\":2}},\"entry_19\":{\"id\":19,\"sku\":\"SKU-526106\",\"name\":\"golf romeo\",\"status\":\"archived\",\"score\":51.51,\"quantity\":176,\"active\":true,\"updatedAt\":\"2026-01-14T12:00:00Z\",\"nested\":{\"tags\":[\"tango\",\"quebec\"],\"depth\":2}},\"entry_20\":{\"id\":20,\"sku\":\"SKU-643873\",\"name\":\"foxtrot tango\",\"status\":\"pending\",\"score\":84.13,\"quantity\":321,\"active\":false,\"updatedAt\":\"2026-05-05T12:00:00Z\",\"nested\":{\"tags\":[\"tango\",\"uniform\"],\"depth\":2}},\"entry_21\":{\"id\":21,\"sku\":\"SKU-881747\",\"name\":\"india charlie\",\"status\":\"active\",\"score\":99.94,\"quantity\":215,\"active\":false,\"updatedAt\":\"2026-03-09T12:00:00Z\",\"nested\":{\"tags\":[\"india\",\"delta\"],\"depth\":2}},\"entry_22\":{\"id\":22,\"sku\":\"SKU-536249\",\"name\":\"oscar echo\",\"status\":\"pending\",\"score\":49.7,\"quantity\":198,\"active\":true,\"updatedAt\":\"2026-02-23T12:00:00Z\",\"nested\":{\"tags\":[\"quebec\",\"juliet\"],\"depth\":2}},\"entry_23\":{\"id\":23,\"sku\":\"SKU-242438\",\"name\":\"zulu alpha\",\"status\":\"failed\",\"score\":15.35,\"quantity\":282,\"active\":false,\"updatedAt\":\"2026-01-11T12:00:00Z\",\"nested\":{\"tags\":[\"november\",\"kilo\"],\"depth\":2}},\"entry_24\":{\"id\":24,\"sku\":\"SKU-787964\",\"name\":\"oscar hotel\",\"status\":\"archived\",\"score\":35.78,\"quantity\":383,\"active\":true,\"updatedAt\":\"2026-04-21T12:00:00Z\",\"nested\":{\"tags\":[\"zulu\",\"uniform\"],\"depth\":2}},\"entry_25\":{\"id\":25,\"sku\":\"SKU-270620\",\"name\":\"india india\",\"status\":\"pending\",\"score\":30.89,\"quantity\":367,\"active\":false,\"updatedAt\":\"2026-04-10T12:00:00Z\",\"nested\":{\"tags\":[\"tango\",\"echo\"],\"depth\":2}},\"entry_26\":{\"id\":26,\"sku\":\"SKU-479773\",\"name\":\"whiskey papa\",\"status\":\"archived\",\"score\":90.09,\"quantity\":10,\"active\":false,\"updatedAt\":\"2026-06-16T12:00:00Z\",\"nested\":{\"tags\":[\"uniform\",\"foxtrot\"],\"depth\":2}},\"entry_27\":{\"id\":27,\"sku\":\"SKU-85068\",\"name\":\"kilo zulu\",\"status\":\"archived\",\"score\":11.57,\"quantity\":100,\"active\":true,\"updatedAt\":\"2026-04-01T12:00:00Z\",\"nested\":{\"tags\":[\"uniform\",\"juliet\"],\"depth\":2}},\"entry_28\":{\"id\":28,\"sku\":\"SKU-88619\",\"name\":\"alpha echo\",\"status\":\"pending\",\"score\":86.22,\"quantity\":23,\"active\":true,\"updatedAt\":\"2026-03-02T12:00:00Z\",\"nested\":{\"tags\":[\"romeo\",\"victor\"],\"depth\":2}},\"entry_29\":{\"id\":29,\"sku\":\"SKU-342557\",\"name\":\"bravo papa\",\"status\":\"failed\",\"score\":57.84,\"quantity\":493,\"active\":false,\"updatedAt\":\"2026-05-15T12:00:00Z\",\"nested\":{\"tags\":[\"echo\",\"foxtrot\"],\"depth\":2}},\"entry_30\":{\"id\":30,\"sku\":\"SKU-64537\",\"name\":\"quebec charlie\",\"status\":\"failed\",\"score\":65.9,\"quantity\":334,\"active\":false,\"updatedAt\":\"2026-04-15T12:00:00Z\",\"nested\":{\"tags\":[\"quebec\",\"juliet\"],\"depth\":2}},\"entry_31\":{\"id\":31,\"sku\":\"SKU-45365\",\"name\":\"papa golf\",\"status\":\"pending\",\"score\":56.24,\"quantity\":315,\"active\":false,\"updatedAt\":\"2026-05-07T12:00:00Z\",\"nested\":{\"tags\":[\"bravo\",\"charlie\"],\"depth\":2}},\"entry_32\":{\"id\":32,\"sku\":\"SKU-306983\",\"name\":\"quebec foxtrot\",\"status\":\"active\",\"score\":6.96,\"quantity\":276,\"active\":true,\"updatedAt\":\"2026-02-17T12:00:00Z\",\"nested\":{\"tags\":[\"romeo\",\"victor\"],\"depth\":2}},\"entry_33\":{\"id\":33,\"sku\":\"SKU-100839\",\"name\":\"alpha lima\",\"status\":\"active\",\"score\":7.02,\"quantity\":308,\"active\":true,\"updatedAt\":\"2026-03-01T12:00:00Z\",\"nested\":{\"tags\":[\"uniform\",\"victor\"],\"depth\":2}},\"entry_34\":{\"id\":34,\"sku\":\"SKU-285603\",\"name\":\"quebec echo\",\"status\":\"pending\",\"score\":54.61,\"quantity\":276,\"active\":false,\"updatedAt\":\"2026-04-12T12:00:00Z\",\"nested\":{\"tags\":[\"echo\",\"kilo\"],\"depth\":2}},\"entry_35\":{\"id\":35,\"sku\":\"SKU-129333\",\"name\":\"foxtrot delta\",\"status\":\"pending\",\"score\":0.69,\"quantity\":36,\"active\":true,\"updatedAt\":\"2026-02-11T12:00:00Z\",\"nested\":{\"tags\":[\"november\",\"sierra\"],\"depth\":2}},\"entry_36\":{\"id\":36,\"sku\":\"SKU-704370\",\"name\":\"sierra oscar\",\"status\":\"active\",\"score\":45.65,\"quantity\":239,\"active\":false,\"updatedAt\":\"2026-01-22T12:00:00Z\",\"nested\":{\"tags\":[\"kilo\",\"oscar\"],\"depth\":2}},\"entry_37\":{\"id\":37,\"sku\":\"SKU-810963\",\"name\":\"foxtrot alpha\",\"status\":\"failed\",\"score\":59.48,\"quantity\":48,\"active\":true,\"updatedAt\":\"2026-06-24T12:00:00Z\",\"nested\":{\"tags\":[\"juliet\",\"zulu\"],\"depth\":2}},\"entry_38\":{\"id\":38,\"sku\":\"SKU-171741\",\"name\":\"zulu uniform\",\"status\":\"failed\",\"score\":42.96,\"quantity\":48,\"active\":true,\"updatedAt\":\"2026-03-22T12:00:00Z\",\"nested\":{\"tags\":[\"delta\",\"charlie\"],\"depth\":2}},\"entry_39\":{\"id\":39,\"sku\":\"SKU-893779\",\"name\":\"alpha tango\",\"status\":\"archived\",\"score\":0.81,\"quantity\":390,\"active\":false,\"updatedAt\":\"2026-06-25T12:00:00Z\",\"nested\":{\"tags\":[\"echo\",\"victor\"],\"depth\":2}},\"entry_40\":{\"id\":40,\"sku\":\"SKU-455048\",\"name\":\"quebec uniform\",\"status\":\"archived\",\"score\":52.81,\"quantity\":296,\"active\":true,\"updatedAt\":\"2026-03-11T12:00:00Z\",\"nested\":{\"tags\":[\"quebec\",\"kilo\"],\"depth\":2}},\"entry_41\":{\"id\":41,\"sku\":\"SKU-579597\",\"name\":\"alpha bravo\",\"status\":\"active\",\"score\":74.63,\"quantity\":209,\"active\":false,\"updatedAt\":\"2026-02-05T12:00:00Z\",\"nested\":{\"tags\":[\"papa\",\"charlie\"],\"depth\":2}},\"entry_42\":{\"id\":42,\"sku\":\"SKU-927995\",\"name\":\"bravo oscar\",\"status\":\"failed\",\"score\":46.03,\"quantity\":13,\"active\":true,\"updatedAt\":\"2026-01-18T12:00:00Z\",\"nested\":{\"tags\":[\"romeo\",\"uniform\"],\"depth\":2}},\"entry_43\":{\"id\":43,\"sku\":\"SKU-640313\",\"name\":\"oscar foxtrot\",\"status\":\"failed\",\"score\":75.06,\"quantity\":102,\"active\":true,\"updatedAt\":\"2026-06-10T12:00:00Z\",\"nested\":{\"tags\":[\"november\",\"quebec\"],\"depth\":2}},\"entry_44\":{\"id\":44,\"sku\":\"SKU-587339\",\"name\":\"delta lima\",\"status\":\"pending\",\"score\":1.02,\"quantity\":160,\"active\":false,\"updatedAt\":\"2026-03-12T12:00:00Z\",\"nested\":{\"tags\":[\"kilo\",\"november\"],\"depth\":2}},\"entry_45\":{\"id\":45,\"sku\":\"SKU-22977\",\"name\":\"alpha charlie\",\"status\":\"active\",\"score\":91.65,\"quantity\":457,\"active\":false,\"updatedAt\":\"2026-02-26T12:00:00Z\",\"nested\":{\"tags\":[\"india\",\"kilo\"],\"depth\":2}},\"entry_46\":{\"id\":46,\"sku\":\"SKU-274156\",\"name\":\"foxtrot oscar\",\"status\":\"archived\",\"score\":31.37,\"quantity\":424,\"active\":true,\"updatedAt\":\"2026-01-24T12:00:00Z\",\"nested\":{\"tags\":[\"alpha\",\"foxtrot\"],\"depth\":2}},\"entry_47\":{\"id\":47,\"sku\":\"SKU-533189\",\"name\":\"golf lima\",\"status\":\"active\",\"score\":9.71,\"quantity\":251,\"active\":false,\"updatedAt\":\"2026-04-01T12:00:00Z\",\"nested\":{\"tags\":[\"india\",\"kilo\"],\"depth\":2}},\"entry_48\":{\"id\":48,\"sku\":\"SKU-290356\",\"name\":\"zulu zulu\",\"status\":\"archived\",\"score\":25.89,\"quantity\":432,\"active\":false,\"updatedAt\":\"2026-04-14T12:00:00Z\",\"nested\":{\"tags\":[\"alpha\",\"kilo\"],\"depth\":2}},\"entry_49\":{\"id\":49,\"sku\":\"SKU-746759\",\"name\":\"juliet tango\",\"status\":\"active\",\"score\":40.83,\"quantity\":374,\"active\":false,\"updatedAt\":\"2026-06-22T12:00:00Z\",\"nested\":{\"tags\":[\"delta\",\"golf\"],\"depth\":2}}}}", + "encoded": "meta:\n source: bench\n version: 3\n total: 50\nentries:\n entry_0:\n id: 0\n sku: \"SKU-509743\"\n name: zulu charlie\n status: archived\n score: 87.86\n quantity: 46\n active: true\n updatedAt: \"2026-02-04T12:00:00Z\"\n nested:\n tags[2]: november,tango\n depth: 2\n entry_1:\n id: 1\n sku: \"SKU-409669\"\n name: quebec zulu\n status: pending\n score: 9.37\n quantity: 361\n active: false\n updatedAt: \"2026-06-27T12:00:00Z\"\n nested:\n tags[2]: golf,victor\n depth: 2\n entry_2:\n id: 2\n sku: \"SKU-564643\"\n name: romeo zulu\n status: pending\n score: 5.29\n quantity: 342\n active: true\n updatedAt: \"2026-03-28T12:00:00Z\"\n nested:\n tags[2]: echo,alpha\n depth: 2\n entry_3:\n id: 3\n sku: \"SKU-49672\"\n name: delta romeo\n status: pending\n score: 58.97\n quantity: 72\n active: true\n updatedAt: \"2026-05-03T12:00:00Z\"\n nested:\n tags[2]: delta,romeo\n depth: 2\n entry_4:\n id: 4\n sku: \"SKU-461045\"\n name: oscar echo\n status: failed\n score: 84.05\n quantity: 44\n active: true\n updatedAt: \"2026-05-01T12:00:00Z\"\n nested:\n tags[2]: whiskey,romeo\n depth: 2\n entry_5:\n id: 5\n sku: \"SKU-434641\"\n name: alpha foxtrot\n status: pending\n score: 21.77\n quantity: 262\n active: false\n updatedAt: \"2026-04-17T12:00:00Z\"\n nested:\n tags[2]: hotel,mike\n depth: 2\n entry_6:\n id: 6\n sku: \"SKU-575426\"\n name: victor golf\n status: archived\n score: 88.95\n quantity: 81\n active: true\n updatedAt: \"2026-01-25T12:00:00Z\"\n nested:\n tags[2]: juliet,alpha\n depth: 2\n entry_7:\n id: 7\n sku: \"SKU-334021\"\n name: romeo oscar\n status: failed\n score: 16.58\n quantity: 67\n active: false\n updatedAt: \"2026-05-25T12:00:00Z\"\n nested:\n tags[2]: juliet,whiskey\n depth: 2\n entry_8:\n id: 8\n sku: \"SKU-540445\"\n name: delta hotel\n status: pending\n score: 53.51\n quantity: 266\n active: true\n updatedAt: \"2026-03-16T12:00:00Z\"\n nested:\n tags[2]: victor,sierra\n depth: 2\n entry_9:\n id: 9\n sku: \"SKU-256829\"\n name: alpha zulu\n status: pending\n score: 9.35\n quantity: 292\n active: true\n updatedAt: \"2026-02-10T12:00:00Z\"\n nested:\n tags[2]: romeo,alpha\n depth: 2\n entry_10:\n id: 10\n sku: \"SKU-950846\"\n name: bravo foxtrot\n status: active\n score: 49.13\n quantity: 26\n active: true\n updatedAt: \"2026-01-04T12:00:00Z\"\n nested:\n tags[2]: delta,india\n depth: 2\n entry_11:\n id: 11\n sku: \"SKU-617755\"\n name: mike zulu\n status: pending\n score: 4.97\n quantity: 415\n active: false\n updatedAt: \"2026-01-17T12:00:00Z\"\n nested:\n tags[2]: mike,echo\n depth: 2\n entry_12:\n id: 12\n sku: \"SKU-581445\"\n name: lima victor\n status: archived\n score: 59.99\n quantity: 485\n active: false\n updatedAt: \"2026-05-05T12:00:00Z\"\n nested:\n tags[2]: alpha,lima\n depth: 2\n entry_13:\n id: 13\n sku: \"SKU-719044\"\n name: oscar kilo\n status: active\n score: 63.28\n quantity: 24\n active: false\n updatedAt: \"2026-05-28T12:00:00Z\"\n nested:\n tags[2]: hotel,mike\n depth: 2\n entry_14:\n id: 14\n sku: \"SKU-789713\"\n name: papa oscar\n status: archived\n score: 27.49\n quantity: 198\n active: false\n updatedAt: \"2026-04-15T12:00:00Z\"\n nested:\n tags[2]: juliet,alpha\n depth: 2\n entry_15:\n id: 15\n sku: \"SKU-534443\"\n name: delta india\n status: archived\n score: 62.51\n quantity: 361\n active: false\n updatedAt: \"2026-05-12T12:00:00Z\"\n nested:\n tags[2]: golf,lima\n depth: 2\n entry_16:\n id: 16\n sku: \"SKU-894299\"\n name: delta golf\n status: active\n score: 4.53\n quantity: 321\n active: false\n updatedAt: \"2026-06-06T12:00:00Z\"\n nested:\n tags[2]: uniform,zulu\n depth: 2\n entry_17:\n id: 17\n sku: \"SKU-178180\"\n name: hotel oscar\n status: active\n score: 94.67\n quantity: 438\n active: false\n updatedAt: \"2026-04-12T12:00:00Z\"\n nested:\n tags[2]: romeo,quebec\n depth: 2\n entry_18:\n id: 18\n sku: \"SKU-474295\"\n name: november juliet\n status: archived\n score: 52.97\n quantity: 162\n active: true\n updatedAt: \"2026-06-12T12:00:00Z\"\n nested:\n tags[2]: foxtrot,romeo\n depth: 2\n entry_19:\n id: 19\n sku: \"SKU-526106\"\n name: golf romeo\n status: archived\n score: 51.51\n quantity: 176\n active: true\n updatedAt: \"2026-01-14T12:00:00Z\"\n nested:\n tags[2]: tango,quebec\n depth: 2\n entry_20:\n id: 20\n sku: \"SKU-643873\"\n name: foxtrot tango\n status: pending\n score: 84.13\n quantity: 321\n active: false\n updatedAt: \"2026-05-05T12:00:00Z\"\n nested:\n tags[2]: tango,uniform\n depth: 2\n entry_21:\n id: 21\n sku: \"SKU-881747\"\n name: india charlie\n status: active\n score: 99.94\n quantity: 215\n active: false\n updatedAt: \"2026-03-09T12:00:00Z\"\n nested:\n tags[2]: india,delta\n depth: 2\n entry_22:\n id: 22\n sku: \"SKU-536249\"\n name: oscar echo\n status: pending\n score: 49.7\n quantity: 198\n active: true\n updatedAt: \"2026-02-23T12:00:00Z\"\n nested:\n tags[2]: quebec,juliet\n depth: 2\n entry_23:\n id: 23\n sku: \"SKU-242438\"\n name: zulu alpha\n status: failed\n score: 15.35\n quantity: 282\n active: false\n updatedAt: \"2026-01-11T12:00:00Z\"\n nested:\n tags[2]: november,kilo\n depth: 2\n entry_24:\n id: 24\n sku: \"SKU-787964\"\n name: oscar hotel\n status: archived\n score: 35.78\n quantity: 383\n active: true\n updatedAt: \"2026-04-21T12:00:00Z\"\n nested:\n tags[2]: zulu,uniform\n depth: 2\n entry_25:\n id: 25\n sku: \"SKU-270620\"\n name: india india\n status: pending\n score: 30.89\n quantity: 367\n active: false\n updatedAt: \"2026-04-10T12:00:00Z\"\n nested:\n tags[2]: tango,echo\n depth: 2\n entry_26:\n id: 26\n sku: \"SKU-479773\"\n name: whiskey papa\n status: archived\n score: 90.09\n quantity: 10\n active: false\n updatedAt: \"2026-06-16T12:00:00Z\"\n nested:\n tags[2]: uniform,foxtrot\n depth: 2\n entry_27:\n id: 27\n sku: \"SKU-85068\"\n name: kilo zulu\n status: archived\n score: 11.57\n quantity: 100\n active: true\n updatedAt: \"2026-04-01T12:00:00Z\"\n nested:\n tags[2]: uniform,juliet\n depth: 2\n entry_28:\n id: 28\n sku: \"SKU-88619\"\n name: alpha echo\n status: pending\n score: 86.22\n quantity: 23\n active: true\n updatedAt: \"2026-03-02T12:00:00Z\"\n nested:\n tags[2]: romeo,victor\n depth: 2\n entry_29:\n id: 29\n sku: \"SKU-342557\"\n name: bravo papa\n status: failed\n score: 57.84\n quantity: 493\n active: false\n updatedAt: \"2026-05-15T12:00:00Z\"\n nested:\n tags[2]: echo,foxtrot\n depth: 2\n entry_30:\n id: 30\n sku: \"SKU-64537\"\n name: quebec charlie\n status: failed\n score: 65.9\n quantity: 334\n active: false\n updatedAt: \"2026-04-15T12:00:00Z\"\n nested:\n tags[2]: quebec,juliet\n depth: 2\n entry_31:\n id: 31\n sku: \"SKU-45365\"\n name: papa golf\n status: pending\n score: 56.24\n quantity: 315\n active: false\n updatedAt: \"2026-05-07T12:00:00Z\"\n nested:\n tags[2]: bravo,charlie\n depth: 2\n entry_32:\n id: 32\n sku: \"SKU-306983\"\n name: quebec foxtrot\n status: active\n score: 6.96\n quantity: 276\n active: true\n updatedAt: \"2026-02-17T12:00:00Z\"\n nested:\n tags[2]: romeo,victor\n depth: 2\n entry_33:\n id: 33\n sku: \"SKU-100839\"\n name: alpha lima\n status: active\n score: 7.02\n quantity: 308\n active: true\n updatedAt: \"2026-03-01T12:00:00Z\"\n nested:\n tags[2]: uniform,victor\n depth: 2\n entry_34:\n id: 34\n sku: \"SKU-285603\"\n name: quebec echo\n status: pending\n score: 54.61\n quantity: 276\n active: false\n updatedAt: \"2026-04-12T12:00:00Z\"\n nested:\n tags[2]: echo,kilo\n depth: 2\n entry_35:\n id: 35\n sku: \"SKU-129333\"\n name: foxtrot delta\n status: pending\n score: 0.69\n quantity: 36\n active: true\n updatedAt: \"2026-02-11T12:00:00Z\"\n nested:\n tags[2]: november,sierra\n depth: 2\n entry_36:\n id: 36\n sku: \"SKU-704370\"\n name: sierra oscar\n status: active\n score: 45.65\n quantity: 239\n active: false\n updatedAt: \"2026-01-22T12:00:00Z\"\n nested:\n tags[2]: kilo,oscar\n depth: 2\n entry_37:\n id: 37\n sku: \"SKU-810963\"\n name: foxtrot alpha\n status: failed\n score: 59.48\n quantity: 48\n active: true\n updatedAt: \"2026-06-24T12:00:00Z\"\n nested:\n tags[2]: juliet,zulu\n depth: 2\n entry_38:\n id: 38\n sku: \"SKU-171741\"\n name: zulu uniform\n status: failed\n score: 42.96\n quantity: 48\n active: true\n updatedAt: \"2026-03-22T12:00:00Z\"\n nested:\n tags[2]: delta,charlie\n depth: 2\n entry_39:\n id: 39\n sku: \"SKU-893779\"\n name: alpha tango\n status: archived\n score: 0.81\n quantity: 390\n active: false\n updatedAt: \"2026-06-25T12:00:00Z\"\n nested:\n tags[2]: echo,victor\n depth: 2\n entry_40:\n id: 40\n sku: \"SKU-455048\"\n name: quebec uniform\n status: archived\n score: 52.81\n quantity: 296\n active: true\n updatedAt: \"2026-03-11T12:00:00Z\"\n nested:\n tags[2]: quebec,kilo\n depth: 2\n entry_41:\n id: 41\n sku: \"SKU-579597\"\n name: alpha bravo\n status: active\n score: 74.63\n quantity: 209\n active: false\n updatedAt: \"2026-02-05T12:00:00Z\"\n nested:\n tags[2]: papa,charlie\n depth: 2\n entry_42:\n id: 42\n sku: \"SKU-927995\"\n name: bravo oscar\n status: failed\n score: 46.03\n quantity: 13\n active: true\n updatedAt: \"2026-01-18T12:00:00Z\"\n nested:\n tags[2]: romeo,uniform\n depth: 2\n entry_43:\n id: 43\n sku: \"SKU-640313\"\n name: oscar foxtrot\n status: failed\n score: 75.06\n quantity: 102\n active: true\n updatedAt: \"2026-06-10T12:00:00Z\"\n nested:\n tags[2]: november,quebec\n depth: 2\n entry_44:\n id: 44\n sku: \"SKU-587339\"\n name: delta lima\n status: pending\n score: 1.02\n quantity: 160\n active: false\n updatedAt: \"2026-03-12T12:00:00Z\"\n nested:\n tags[2]: kilo,november\n depth: 2\n entry_45:\n id: 45\n sku: \"SKU-22977\"\n name: alpha charlie\n status: active\n score: 91.65\n quantity: 457\n active: false\n updatedAt: \"2026-02-26T12:00:00Z\"\n nested:\n tags[2]: india,kilo\n depth: 2\n entry_46:\n id: 46\n sku: \"SKU-274156\"\n name: foxtrot oscar\n status: archived\n score: 31.37\n quantity: 424\n active: true\n updatedAt: \"2026-01-24T12:00:00Z\"\n nested:\n tags[2]: alpha,foxtrot\n depth: 2\n entry_47:\n id: 47\n sku: \"SKU-533189\"\n name: golf lima\n status: active\n score: 9.71\n quantity: 251\n active: false\n updatedAt: \"2026-04-01T12:00:00Z\"\n nested:\n tags[2]: india,kilo\n depth: 2\n entry_48:\n id: 48\n sku: \"SKU-290356\"\n name: zulu zulu\n status: archived\n score: 25.89\n quantity: 432\n active: false\n updatedAt: \"2026-04-14T12:00:00Z\"\n nested:\n tags[2]: alpha,kilo\n depth: 2\n entry_49:\n id: 49\n sku: \"SKU-746759\"\n name: juliet tango\n status: active\n score: 40.83\n quantity: 374\n active: false\n updatedAt: \"2026-06-22T12:00:00Z\"\n nested:\n tags[2]: delta,golf\n depth: 2" + } + }, + { + "name": "boundary-obj-1", + "rawContent": "{\"key_0\":\"val, with comma 0\"}", + "unwrap": true, + "expected": { + "normalized": "{\"key_0\":\"val, with comma 0\"}", + "encoded": "key_0: \"val, with comma 0\"" + } + }, + { + "name": "boundary-obj-2", + "rawContent": "{\"key_0\":\"val, with comma 0\",\"key_1\":1.5}", + "unwrap": true, + "expected": { + "normalized": "{\"key_0\":\"val, with comma 0\",\"key_1\":1.5}", + "encoded": "key_0: \"val, with comma 0\"\nkey_1: 1.5" + } + }, + { + "name": "boundary-obj-3", + "rawContent": "{\"key_0\":\"val, with comma 0\",\"key_1\":1.5,\"key_2\":\"val, with comma 2\"}", + "unwrap": true, + "expected": { + "normalized": "{\"key_0\":\"val, with comma 0\",\"key_1\":1.5,\"key_2\":\"val, with comma 2\"}", + "encoded": "key_0: \"val, with comma 0\"\nkey_1: 1.5\nkey_2: \"val, with comma 2\"" + } + }, + { + "name": "boundary-obj-4", + "rawContent": "{\"key_0\":\"val, with comma 0\",\"key_1\":1.5,\"key_2\":\"val, with comma 2\",\"key_3\":4.5}", + "unwrap": true, + "expected": { + "normalized": "{\"key_0\":\"val, with comma 0\",\"key_1\":1.5,\"key_2\":\"val, with comma 2\",\"key_3\":4.5}", + "encoded": "key_0: \"val, with comma 0\"\nkey_1: 1.5\nkey_2: \"val, with comma 2\"\nkey_3: 4.5" + } + }, + { + "name": "boundary-obj-5", + "rawContent": "{\"key_0\":\"val, with comma 0\",\"key_1\":1.5,\"key_2\":\"val, with comma 2\",\"key_3\":4.5,\"key_4\":\"val, with comma 4\"}", + "unwrap": true, + "expected": { + "normalized": "{\"key_0\":\"val, with comma 0\",\"key_1\":1.5,\"key_2\":\"val, with comma 2\",\"key_3\":4.5,\"key_4\":\"val, with comma 4\"}", + "encoded": "key_0: \"val, with comma 0\"\nkey_1: 1.5\nkey_2: \"val, with comma 2\"\nkey_3: 4.5\nkey_4: \"val, with comma 4\"" + } + }, + { + "name": "boundary-obj-6", + "rawContent": "{\"key_0\":\"val, with comma 0\",\"key_1\":1.5,\"key_2\":\"val, with comma 2\",\"key_3\":4.5,\"key_4\":\"val, with comma 4\",\"key_5\":7.5}", + "unwrap": true, + "expected": { + "normalized": "{\"key_0\":\"val, with comma 0\",\"key_1\":1.5,\"key_2\":\"val, with comma 2\",\"key_3\":4.5,\"key_4\":\"val, with comma 4\",\"key_5\":7.5}", + "encoded": "key_0: \"val, with comma 0\"\nkey_1: 1.5\nkey_2: \"val, with comma 2\"\nkey_3: 4.5\nkey_4: \"val, with comma 4\"\nkey_5: 7.5" + } + }, + { + "name": "boundary-obj-7", + "rawContent": "{\"key_0\":\"val, with comma 0\",\"key_1\":1.5,\"key_2\":\"val, with comma 2\",\"key_3\":4.5,\"key_4\":\"val, with comma 4\",\"key_5\":7.5,\"key_6\":\"val, with comma 6\"}", + "unwrap": true, + "expected": { + "normalized": "{\"key_0\":\"val, with comma 0\",\"key_1\":1.5,\"key_2\":\"val, with comma 2\",\"key_3\":4.5,\"key_4\":\"val, with comma 4\",\"key_5\":7.5,\"key_6\":\"val, with comma 6\"}", + "encoded": "key_0: \"val, with comma 0\"\nkey_1: 1.5\nkey_2: \"val, with comma 2\"\nkey_3: 4.5\nkey_4: \"val, with comma 4\"\nkey_5: 7.5\nkey_6: \"val, with comma 6\"" + } + }, + { + "name": "boundary-obj-8", + "rawContent": "{\"key_0\":\"val, with comma 0\",\"key_1\":1.5,\"key_2\":\"val, with comma 2\",\"key_3\":4.5,\"key_4\":\"val, with comma 4\",\"key_5\":7.5,\"key_6\":\"val, with comma 6\",\"key_7\":10.5}", + "unwrap": true, + "expected": { + "normalized": "{\"key_0\":\"val, with comma 0\",\"key_1\":1.5,\"key_2\":\"val, with comma 2\",\"key_3\":4.5,\"key_4\":\"val, with comma 4\",\"key_5\":7.5,\"key_6\":\"val, with comma 6\",\"key_7\":10.5}", + "encoded": "key_0: \"val, with comma 0\"\nkey_1: 1.5\nkey_2: \"val, with comma 2\"\nkey_3: 4.5\nkey_4: \"val, with comma 4\"\nkey_5: 7.5\nkey_6: \"val, with comma 6\"\nkey_7: 10.5" + } + }, + { + "name": "boundary-obj-9", + "rawContent": "{\"key_0\":\"val, with comma 0\",\"key_1\":1.5,\"key_2\":\"val, with comma 2\",\"key_3\":4.5,\"key_4\":\"val, with comma 4\",\"key_5\":7.5,\"key_6\":\"val, with comma 6\",\"key_7\":10.5,\"key_8\":\"val, with comma 8\"}", + "unwrap": true, + "expected": { + "normalized": "{\"key_0\":\"val, with comma 0\",\"key_1\":1.5,\"key_2\":\"val, with comma 2\",\"key_3\":4.5,\"key_4\":\"val, with comma 4\",\"key_5\":7.5,\"key_6\":\"val, with comma 6\",\"key_7\":10.5,\"key_8\":\"val, with comma 8\"}", + "encoded": "key_0: \"val, with comma 0\"\nkey_1: 1.5\nkey_2: \"val, with comma 2\"\nkey_3: 4.5\nkey_4: \"val, with comma 4\"\nkey_5: 7.5\nkey_6: \"val, with comma 6\"\nkey_7: 10.5\nkey_8: \"val, with comma 8\"" + } + }, + { + "name": "boundary-obj-10", + "rawContent": "{\"key_0\":\"val, with comma 0\",\"key_1\":1.5,\"key_2\":\"val, with comma 2\",\"key_3\":4.5,\"key_4\":\"val, with comma 4\",\"key_5\":7.5,\"key_6\":\"val, with comma 6\",\"key_7\":10.5,\"key_8\":\"val, with comma 8\",\"key_9\":13.5}", + "unwrap": true, + "expected": { + "normalized": "{\"key_0\":\"val, with comma 0\",\"key_1\":1.5,\"key_2\":\"val, with comma 2\",\"key_3\":4.5,\"key_4\":\"val, with comma 4\",\"key_5\":7.5,\"key_6\":\"val, with comma 6\",\"key_7\":10.5,\"key_8\":\"val, with comma 8\",\"key_9\":13.5}", + "encoded": "key_0: \"val, with comma 0\"\nkey_1: 1.5\nkey_2: \"val, with comma 2\"\nkey_3: 4.5\nkey_4: \"val, with comma 4\"\nkey_5: 7.5\nkey_6: \"val, with comma 6\"\nkey_7: 10.5\nkey_8: \"val, with comma 8\"\nkey_9: 13.5" + } + }, + { + "name": "boundary-hetero-arr-1", + "rawContent": "[{\"f0\":\"x,y 0\",\"n\":0}]", + "unwrap": true, + "expected": { + "normalized": "[{\"f0\":\"x,y 0\",\"n\":0}]", + "encoded": "[1]{f0,n}:\n \"x,y 0\",0" + } + }, + { + "name": "boundary-hetero-arr-2", + "rawContent": "[{\"f0\":\"x,y 0\",\"n\":0},{\"f1\":\"x,y 1\",\"n\":1}]", + "unwrap": true, + "expected": { + "normalized": "[{\"f0\":\"x,y 0\",\"n\":0},{\"f1\":\"x,y 1\",\"n\":1}]", + "encoded": "[2]:\n - f0: \"x,y 0\"\n n: 0\n - f1: \"x,y 1\"\n n: 1" + } + }, + { + "name": "boundary-hetero-arr-3", + "rawContent": "[{\"f0\":\"x,y 0\",\"n\":0},{\"f1\":\"x,y 1\",\"n\":1},{\"f2\":\"x,y 2\",\"n\":2}]", + "unwrap": true, + "expected": { + "normalized": "[{\"f0\":\"x,y 0\",\"n\":0},{\"f1\":\"x,y 1\",\"n\":1},{\"f2\":\"x,y 2\",\"n\":2}]", + "encoded": "[3]:\n - f0: \"x,y 0\"\n n: 0\n - f1: \"x,y 1\"\n n: 1\n - f2: \"x,y 2\"\n n: 2" + } + }, + { + "name": "boundary-hetero-arr-4", + "rawContent": "[{\"f0\":\"x,y 0\",\"n\":0},{\"f1\":\"x,y 1\",\"n\":1},{\"f2\":\"x,y 2\",\"n\":2},{\"f3\":\"x,y 3\",\"n\":3}]", + "unwrap": true, + "expected": { + "normalized": "[{\"f0\":\"x,y 0\",\"n\":0},{\"f1\":\"x,y 1\",\"n\":1},{\"f2\":\"x,y 2\",\"n\":2},{\"f3\":\"x,y 3\",\"n\":3}]", + "encoded": "[4]:\n - f0: \"x,y 0\"\n n: 0\n - f1: \"x,y 1\"\n n: 1\n - f2: \"x,y 2\"\n n: 2\n - f3: \"x,y 3\"\n n: 3" + } + }, + { + "name": "boundary-hetero-arr-5", + "rawContent": "[{\"f0\":\"x,y 0\",\"n\":0},{\"f1\":\"x,y 1\",\"n\":1},{\"f2\":\"x,y 2\",\"n\":2},{\"f3\":\"x,y 3\",\"n\":3},{\"f4\":\"x,y 4\",\"n\":4}]", + "unwrap": true, + "expected": { + "normalized": "[{\"f0\":\"x,y 0\",\"n\":0},{\"f1\":\"x,y 1\",\"n\":1},{\"f2\":\"x,y 2\",\"n\":2},{\"f3\":\"x,y 3\",\"n\":3},{\"f4\":\"x,y 4\",\"n\":4}]", + "encoded": "[5]:\n - f0: \"x,y 0\"\n n: 0\n - f1: \"x,y 1\"\n n: 1\n - f2: \"x,y 2\"\n n: 2\n - f3: \"x,y 3\"\n n: 3\n - f4: \"x,y 4\"\n n: 4" + } + }, + { + "name": "boundary-hetero-arr-6", + "rawContent": "[{\"f0\":\"x,y 0\",\"n\":0},{\"f1\":\"x,y 1\",\"n\":1},{\"f2\":\"x,y 2\",\"n\":2},{\"f3\":\"x,y 3\",\"n\":3},{\"f4\":\"x,y 4\",\"n\":4},{\"f5\":\"x,y 5\",\"n\":5}]", + "unwrap": true, + "expected": { + "normalized": "[{\"f0\":\"x,y 0\",\"n\":0},{\"f1\":\"x,y 1\",\"n\":1},{\"f2\":\"x,y 2\",\"n\":2},{\"f3\":\"x,y 3\",\"n\":3},{\"f4\":\"x,y 4\",\"n\":4},{\"f5\":\"x,y 5\",\"n\":5}]", + "encoded": "[6]:\n - f0: \"x,y 0\"\n n: 0\n - f1: \"x,y 1\"\n n: 1\n - f2: \"x,y 2\"\n n: 2\n - f3: \"x,y 3\"\n n: 3\n - f4: \"x,y 4\"\n n: 4\n - f5: \"x,y 5\"\n n: 5" + } + }, + { + "name": "boundary-hyphen-arr-1", + "rawContent": "[{\"sku\":\"AB-100\",\"zone\":\"us-east-0\",\"n\":0}]", + "unwrap": true, + "expected": { + "normalized": "[{\"sku\":\"AB-100\",\"zone\":\"us-east-0\",\"n\":0}]", + "encoded": "[1]{sku,zone,n}:\n \"AB-100\",\"us-east-0\",0" + } + }, + { + "name": "boundary-hyphen-arr-2", + "rawContent": "[{\"sku\":\"AB-100\",\"zone\":\"us-east-0\",\"n\":0},{\"sku\":\"AB-101\",\"zone\":\"us-east-1\",\"n\":1}]", + "unwrap": true, + "expected": { + "normalized": "[{\"sku\":\"AB-100\",\"zone\":\"us-east-0\",\"n\":0},{\"sku\":\"AB-101\",\"zone\":\"us-east-1\",\"n\":1}]", + "encoded": "[2]{sku,zone,n}:\n \"AB-100\",\"us-east-0\",0\n \"AB-101\",\"us-east-1\",1" + } + }, + { + "name": "boundary-hyphen-arr-3", + "rawContent": "[{\"sku\":\"AB-100\",\"zone\":\"us-east-0\",\"n\":0},{\"sku\":\"AB-101\",\"zone\":\"us-east-1\",\"n\":1},{\"sku\":\"AB-102\",\"zone\":\"us-east-2\",\"n\":2}]", + "unwrap": true, + "expected": { + "normalized": "[{\"sku\":\"AB-100\",\"zone\":\"us-east-0\",\"n\":0},{\"sku\":\"AB-101\",\"zone\":\"us-east-1\",\"n\":1},{\"sku\":\"AB-102\",\"zone\":\"us-east-2\",\"n\":2}]", + "encoded": "[3]{sku,zone,n}:\n \"AB-100\",\"us-east-0\",0\n \"AB-101\",\"us-east-1\",1\n \"AB-102\",\"us-east-2\",2" + } + }, + { + "name": "boundary-hyphen-arr-4", + "rawContent": "[{\"sku\":\"AB-100\",\"zone\":\"us-east-0\",\"n\":0},{\"sku\":\"AB-101\",\"zone\":\"us-east-1\",\"n\":1},{\"sku\":\"AB-102\",\"zone\":\"us-east-2\",\"n\":2},{\"sku\":\"AB-103\",\"zone\":\"us-east-3\",\"n\":3}]", + "unwrap": true, + "expected": { + "normalized": "[{\"sku\":\"AB-100\",\"zone\":\"us-east-0\",\"n\":0},{\"sku\":\"AB-101\",\"zone\":\"us-east-1\",\"n\":1},{\"sku\":\"AB-102\",\"zone\":\"us-east-2\",\"n\":2},{\"sku\":\"AB-103\",\"zone\":\"us-east-3\",\"n\":3}]", + "encoded": "[4]{sku,zone,n}:\n \"AB-100\",\"us-east-0\",0\n \"AB-101\",\"us-east-1\",1\n \"AB-102\",\"us-east-2\",2\n \"AB-103\",\"us-east-3\",3" + } + }, + { + "name": "boundary-hyphen-arr-5", + "rawContent": "[{\"sku\":\"AB-100\",\"zone\":\"us-east-0\",\"n\":0},{\"sku\":\"AB-101\",\"zone\":\"us-east-1\",\"n\":1},{\"sku\":\"AB-102\",\"zone\":\"us-east-2\",\"n\":2},{\"sku\":\"AB-103\",\"zone\":\"us-east-3\",\"n\":3},{\"sku\":\"AB-104\",\"zone\":\"us-east-4\",\"n\":4}]", + "unwrap": true, + "expected": { + "normalized": "[{\"sku\":\"AB-100\",\"zone\":\"us-east-0\",\"n\":0},{\"sku\":\"AB-101\",\"zone\":\"us-east-1\",\"n\":1},{\"sku\":\"AB-102\",\"zone\":\"us-east-2\",\"n\":2},{\"sku\":\"AB-103\",\"zone\":\"us-east-3\",\"n\":3},{\"sku\":\"AB-104\",\"zone\":\"us-east-4\",\"n\":4}]", + "encoded": "[5]{sku,zone,n}:\n \"AB-100\",\"us-east-0\",0\n \"AB-101\",\"us-east-1\",1\n \"AB-102\",\"us-east-2\",2\n \"AB-103\",\"us-east-3\",3\n \"AB-104\",\"us-east-4\",4" + } + }, + { + "name": "boundary-hyphen-arr-6", + "rawContent": "[{\"sku\":\"AB-100\",\"zone\":\"us-east-0\",\"n\":0},{\"sku\":\"AB-101\",\"zone\":\"us-east-1\",\"n\":1},{\"sku\":\"AB-102\",\"zone\":\"us-east-2\",\"n\":2},{\"sku\":\"AB-103\",\"zone\":\"us-east-3\",\"n\":3},{\"sku\":\"AB-104\",\"zone\":\"us-east-4\",\"n\":4},{\"sku\":\"AB-105\",\"zone\":\"us-east-5\",\"n\":5}]", + "unwrap": true, + "expected": { + "normalized": "[{\"sku\":\"AB-100\",\"zone\":\"us-east-0\",\"n\":0},{\"sku\":\"AB-101\",\"zone\":\"us-east-1\",\"n\":1},{\"sku\":\"AB-102\",\"zone\":\"us-east-2\",\"n\":2},{\"sku\":\"AB-103\",\"zone\":\"us-east-3\",\"n\":3},{\"sku\":\"AB-104\",\"zone\":\"us-east-4\",\"n\":4},{\"sku\":\"AB-105\",\"zone\":\"us-east-5\",\"n\":5}]", + "encoded": "[6]{sku,zone,n}:\n \"AB-100\",\"us-east-0\",0\n \"AB-101\",\"us-east-1\",1\n \"AB-102\",\"us-east-2\",2\n \"AB-103\",\"us-east-3\",3\n \"AB-104\",\"us-east-4\",4\n \"AB-105\",\"us-east-5\",5" + } + }, + { + "name": "boundary-hyphen-arr-7", + "rawContent": "[{\"sku\":\"AB-100\",\"zone\":\"us-east-0\",\"n\":0},{\"sku\":\"AB-101\",\"zone\":\"us-east-1\",\"n\":1},{\"sku\":\"AB-102\",\"zone\":\"us-east-2\",\"n\":2},{\"sku\":\"AB-103\",\"zone\":\"us-east-3\",\"n\":3},{\"sku\":\"AB-104\",\"zone\":\"us-east-4\",\"n\":4},{\"sku\":\"AB-105\",\"zone\":\"us-east-5\",\"n\":5},{\"sku\":\"AB-106\",\"zone\":\"us-east-6\",\"n\":6}]", + "unwrap": true, + "expected": { + "normalized": "[{\"sku\":\"AB-100\",\"zone\":\"us-east-0\",\"n\":0},{\"sku\":\"AB-101\",\"zone\":\"us-east-1\",\"n\":1},{\"sku\":\"AB-102\",\"zone\":\"us-east-2\",\"n\":2},{\"sku\":\"AB-103\",\"zone\":\"us-east-3\",\"n\":3},{\"sku\":\"AB-104\",\"zone\":\"us-east-4\",\"n\":4},{\"sku\":\"AB-105\",\"zone\":\"us-east-5\",\"n\":5},{\"sku\":\"AB-106\",\"zone\":\"us-east-6\",\"n\":6}]", + "encoded": "[7]{sku,zone,n}:\n \"AB-100\",\"us-east-0\",0\n \"AB-101\",\"us-east-1\",1\n \"AB-102\",\"us-east-2\",2\n \"AB-103\",\"us-east-3\",3\n \"AB-104\",\"us-east-4\",4\n \"AB-105\",\"us-east-5\",5\n \"AB-106\",\"us-east-6\",6" + } + }, + { + "name": "boundary-hyphen-arr-8", + "rawContent": "[{\"sku\":\"AB-100\",\"zone\":\"us-east-0\",\"n\":0},{\"sku\":\"AB-101\",\"zone\":\"us-east-1\",\"n\":1},{\"sku\":\"AB-102\",\"zone\":\"us-east-2\",\"n\":2},{\"sku\":\"AB-103\",\"zone\":\"us-east-3\",\"n\":3},{\"sku\":\"AB-104\",\"zone\":\"us-east-4\",\"n\":4},{\"sku\":\"AB-105\",\"zone\":\"us-east-5\",\"n\":5},{\"sku\":\"AB-106\",\"zone\":\"us-east-6\",\"n\":6},{\"sku\":\"AB-107\",\"zone\":\"us-east-7\",\"n\":7}]", + "unwrap": true, + "expected": { + "normalized": "[{\"sku\":\"AB-100\",\"zone\":\"us-east-0\",\"n\":0},{\"sku\":\"AB-101\",\"zone\":\"us-east-1\",\"n\":1},{\"sku\":\"AB-102\",\"zone\":\"us-east-2\",\"n\":2},{\"sku\":\"AB-103\",\"zone\":\"us-east-3\",\"n\":3},{\"sku\":\"AB-104\",\"zone\":\"us-east-4\",\"n\":4},{\"sku\":\"AB-105\",\"zone\":\"us-east-5\",\"n\":5},{\"sku\":\"AB-106\",\"zone\":\"us-east-6\",\"n\":6},{\"sku\":\"AB-107\",\"zone\":\"us-east-7\",\"n\":7}]", + "encoded": "[8]{sku,zone,n}:\n \"AB-100\",\"us-east-0\",0\n \"AB-101\",\"us-east-1\",1\n \"AB-102\",\"us-east-2\",2\n \"AB-103\",\"us-east-3\",3\n \"AB-104\",\"us-east-4\",4\n \"AB-105\",\"us-east-5\",5\n \"AB-106\",\"us-east-6\",6\n \"AB-107\",\"us-east-7\",7" + } + }, + { + "name": "fine-r1-b0-h1", + "rawContent": "[{\"h0\":\"us-east-00\"}]", + "unwrap": true, + "expected": { + "normalized": "[{\"h0\":\"us-east-00\"}]", + "encoded": "[1]{h0}:\n \"us-east-00\"" + } + }, + { + "name": "fine-r2-b0-h1", + "rawContent": "[{\"h0\":\"us-east-00\"},{\"h0\":\"us-east-10\"}]", + "unwrap": true, + "expected": { + "normalized": "[{\"h0\":\"us-east-00\"},{\"h0\":\"us-east-10\"}]", + "encoded": "[2]{h0}:\n \"us-east-00\"\n \"us-east-10\"" + } + }, + { + "name": "fine-r1-b0-h2", + "rawContent": "[{\"h0\":\"us-east-00\",\"h1\":\"us-east-01\"}]", + "unwrap": true, + "expected": { + "normalized": "[{\"h0\":\"us-east-00\",\"h1\":\"us-east-01\"}]", + "encoded": "[1]{h0,h1}:\n \"us-east-00\",\"us-east-01\"" + } + }, + { + "name": "fine-r2-b0-h2", + "rawContent": "[{\"h0\":\"us-east-00\",\"h1\":\"us-east-01\"},{\"h0\":\"us-east-10\",\"h1\":\"us-east-11\"}]", + "unwrap": true, + "expected": { + "normalized": "[{\"h0\":\"us-east-00\",\"h1\":\"us-east-01\"},{\"h0\":\"us-east-10\",\"h1\":\"us-east-11\"}]", + "encoded": "[2]{h0,h1}:\n \"us-east-00\",\"us-east-01\"\n \"us-east-10\",\"us-east-11\"" + } + }, + { + "name": "fine-r1-b0-h3", + "rawContent": "[{\"h0\":\"us-east-00\",\"h1\":\"us-east-01\",\"h2\":\"us-east-02\"}]", + "unwrap": true, + "expected": { + "normalized": "[{\"h0\":\"us-east-00\",\"h1\":\"us-east-01\",\"h2\":\"us-east-02\"}]", + "encoded": "[1]{h0,h1,h2}:\n \"us-east-00\",\"us-east-01\",\"us-east-02\"" + } + }, + { + "name": "fine-r2-b0-h3", + "rawContent": "[{\"h0\":\"us-east-00\",\"h1\":\"us-east-01\",\"h2\":\"us-east-02\"},{\"h0\":\"us-east-10\",\"h1\":\"us-east-11\",\"h2\":\"us-east-12\"}]", + "unwrap": true, + "expected": { + "normalized": "[{\"h0\":\"us-east-00\",\"h1\":\"us-east-01\",\"h2\":\"us-east-02\"},{\"h0\":\"us-east-10\",\"h1\":\"us-east-11\",\"h2\":\"us-east-12\"}]", + "encoded": "[2]{h0,h1,h2}:\n \"us-east-00\",\"us-east-01\",\"us-east-02\"\n \"us-east-10\",\"us-east-11\",\"us-east-12\"" + } + }, + { + "name": "fine-r1-b1-h1", + "rawContent": "[{\"b0\":\"plain00\",\"h0\":\"us-east-00\"}]", + "unwrap": true, + "expected": { + "normalized": "[{\"b0\":\"plain00\",\"h0\":\"us-east-00\"}]", + "encoded": "[1]{b0,h0}:\n plain00,\"us-east-00\"" + } + }, + { + "name": "fine-r2-b1-h1", + "rawContent": "[{\"b0\":\"plain00\",\"h0\":\"us-east-00\"},{\"b0\":\"plain10\",\"h0\":\"us-east-10\"}]", + "unwrap": true, + "expected": { + "normalized": "[{\"b0\":\"plain00\",\"h0\":\"us-east-00\"},{\"b0\":\"plain10\",\"h0\":\"us-east-10\"}]", + "encoded": "[2]{b0,h0}:\n plain00,\"us-east-00\"\n plain10,\"us-east-10\"" + } + }, + { + "name": "fine-r1-b1-h2", + "rawContent": "[{\"b0\":\"plain00\",\"h0\":\"us-east-00\",\"h1\":\"us-east-01\"}]", + "unwrap": true, + "expected": { + "normalized": "[{\"b0\":\"plain00\",\"h0\":\"us-east-00\",\"h1\":\"us-east-01\"}]", + "encoded": "[1]{b0,h0,h1}:\n plain00,\"us-east-00\",\"us-east-01\"" + } + }, + { + "name": "fine-r2-b1-h2", + "rawContent": "[{\"b0\":\"plain00\",\"h0\":\"us-east-00\",\"h1\":\"us-east-01\"},{\"b0\":\"plain10\",\"h0\":\"us-east-10\",\"h1\":\"us-east-11\"}]", + "unwrap": true, + "expected": { + "normalized": "[{\"b0\":\"plain00\",\"h0\":\"us-east-00\",\"h1\":\"us-east-01\"},{\"b0\":\"plain10\",\"h0\":\"us-east-10\",\"h1\":\"us-east-11\"}]", + "encoded": "[2]{b0,h0,h1}:\n plain00,\"us-east-00\",\"us-east-01\"\n plain10,\"us-east-10\",\"us-east-11\"" + } + }, + { + "name": "fine-r1-b1-h3", + "rawContent": "[{\"b0\":\"plain00\",\"h0\":\"us-east-00\",\"h1\":\"us-east-01\",\"h2\":\"us-east-02\"}]", + "unwrap": true, + "expected": { + "normalized": "[{\"b0\":\"plain00\",\"h0\":\"us-east-00\",\"h1\":\"us-east-01\",\"h2\":\"us-east-02\"}]", + "encoded": "[1]{b0,h0,h1,h2}:\n plain00,\"us-east-00\",\"us-east-01\",\"us-east-02\"" + } + }, + { + "name": "fine-r2-b1-h3", + "rawContent": "[{\"b0\":\"plain00\",\"h0\":\"us-east-00\",\"h1\":\"us-east-01\",\"h2\":\"us-east-02\"},{\"b0\":\"plain10\",\"h0\":\"us-east-10\",\"h1\":\"us-east-11\",\"h2\":\"us-east-12\"}]", + "unwrap": true, + "expected": { + "normalized": "[{\"b0\":\"plain00\",\"h0\":\"us-east-00\",\"h1\":\"us-east-01\",\"h2\":\"us-east-02\"},{\"b0\":\"plain10\",\"h0\":\"us-east-10\",\"h1\":\"us-east-11\",\"h2\":\"us-east-12\"}]", + "encoded": "[2]{b0,h0,h1,h2}:\n plain00,\"us-east-00\",\"us-east-01\",\"us-east-02\"\n plain10,\"us-east-10\",\"us-east-11\",\"us-east-12\"" + } + }, + { + "name": "fine-r1-b2-h1", + "rawContent": "[{\"b0\":\"plain00\",\"b1\":\"plain01\",\"h0\":\"us-east-00\"}]", + "unwrap": true, + "expected": { + "normalized": "[{\"b0\":\"plain00\",\"b1\":\"plain01\",\"h0\":\"us-east-00\"}]", + "encoded": "[1]{b0,b1,h0}:\n plain00,plain01,\"us-east-00\"" + } + }, + { + "name": "fine-r2-b2-h1", + "rawContent": "[{\"b0\":\"plain00\",\"b1\":\"plain01\",\"h0\":\"us-east-00\"},{\"b0\":\"plain10\",\"b1\":\"plain11\",\"h0\":\"us-east-10\"}]", + "unwrap": true, + "expected": { + "normalized": "[{\"b0\":\"plain00\",\"b1\":\"plain01\",\"h0\":\"us-east-00\"},{\"b0\":\"plain10\",\"b1\":\"plain11\",\"h0\":\"us-east-10\"}]", + "encoded": "[2]{b0,b1,h0}:\n plain00,plain01,\"us-east-00\"\n plain10,plain11,\"us-east-10\"" + } + }, + { + "name": "fine-r1-b2-h2", + "rawContent": "[{\"b0\":\"plain00\",\"b1\":\"plain01\",\"h0\":\"us-east-00\",\"h1\":\"us-east-01\"}]", + "unwrap": true, + "expected": { + "normalized": "[{\"b0\":\"plain00\",\"b1\":\"plain01\",\"h0\":\"us-east-00\",\"h1\":\"us-east-01\"}]", + "encoded": "[1]{b0,b1,h0,h1}:\n plain00,plain01,\"us-east-00\",\"us-east-01\"" + } + }, + { + "name": "fine-r2-b2-h2", + "rawContent": "[{\"b0\":\"plain00\",\"b1\":\"plain01\",\"h0\":\"us-east-00\",\"h1\":\"us-east-01\"},{\"b0\":\"plain10\",\"b1\":\"plain11\",\"h0\":\"us-east-10\",\"h1\":\"us-east-11\"}]", + "unwrap": true, + "expected": { + "normalized": "[{\"b0\":\"plain00\",\"b1\":\"plain01\",\"h0\":\"us-east-00\",\"h1\":\"us-east-01\"},{\"b0\":\"plain10\",\"b1\":\"plain11\",\"h0\":\"us-east-10\",\"h1\":\"us-east-11\"}]", + "encoded": "[2]{b0,b1,h0,h1}:\n plain00,plain01,\"us-east-00\",\"us-east-01\"\n plain10,plain11,\"us-east-10\",\"us-east-11\"" + } + }, + { + "name": "fine-r1-b2-h3", + "rawContent": "[{\"b0\":\"plain00\",\"b1\":\"plain01\",\"h0\":\"us-east-00\",\"h1\":\"us-east-01\",\"h2\":\"us-east-02\"}]", + "unwrap": true, + "expected": { + "normalized": "[{\"b0\":\"plain00\",\"b1\":\"plain01\",\"h0\":\"us-east-00\",\"h1\":\"us-east-01\",\"h2\":\"us-east-02\"}]", + "encoded": "[1]{b0,b1,h0,h1,h2}:\n plain00,plain01,\"us-east-00\",\"us-east-01\",\"us-east-02\"" + } + }, + { + "name": "fine-r2-b2-h3", + "rawContent": "[{\"b0\":\"plain00\",\"b1\":\"plain01\",\"h0\":\"us-east-00\",\"h1\":\"us-east-01\",\"h2\":\"us-east-02\"},{\"b0\":\"plain10\",\"b1\":\"plain11\",\"h0\":\"us-east-10\",\"h1\":\"us-east-11\",\"h2\":\"us-east-12\"}]", + "unwrap": true, + "expected": { + "normalized": "[{\"b0\":\"plain00\",\"b1\":\"plain01\",\"h0\":\"us-east-00\",\"h1\":\"us-east-01\",\"h2\":\"us-east-02\"},{\"b0\":\"plain10\",\"b1\":\"plain11\",\"h0\":\"us-east-10\",\"h1\":\"us-east-11\",\"h2\":\"us-east-12\"}]", + "encoded": "[2]{b0,b1,h0,h1,h2}:\n plain00,plain01,\"us-east-00\",\"us-east-01\",\"us-east-02\"\n plain10,plain11,\"us-east-10\",\"us-east-11\",\"us-east-12\"" + } + }, + { + "name": "fine-r1-b3-h1", + "rawContent": "[{\"b0\":\"plain00\",\"b1\":\"plain01\",\"b2\":\"plain02\",\"h0\":\"us-east-00\"}]", + "unwrap": true, + "expected": { + "normalized": "[{\"b0\":\"plain00\",\"b1\":\"plain01\",\"b2\":\"plain02\",\"h0\":\"us-east-00\"}]", + "encoded": "[1]{b0,b1,b2,h0}:\n plain00,plain01,plain02,\"us-east-00\"" + } + }, + { + "name": "fine-r2-b3-h1", + "rawContent": "[{\"b0\":\"plain00\",\"b1\":\"plain01\",\"b2\":\"plain02\",\"h0\":\"us-east-00\"},{\"b0\":\"plain10\",\"b1\":\"plain11\",\"b2\":\"plain12\",\"h0\":\"us-east-10\"}]", + "unwrap": true, + "expected": { + "normalized": "[{\"b0\":\"plain00\",\"b1\":\"plain01\",\"b2\":\"plain02\",\"h0\":\"us-east-00\"},{\"b0\":\"plain10\",\"b1\":\"plain11\",\"b2\":\"plain12\",\"h0\":\"us-east-10\"}]", + "encoded": "[2]{b0,b1,b2,h0}:\n plain00,plain01,plain02,\"us-east-00\"\n plain10,plain11,plain12,\"us-east-10\"" + } + }, + { + "name": "fine-r1-b3-h2", + "rawContent": "[{\"b0\":\"plain00\",\"b1\":\"plain01\",\"b2\":\"plain02\",\"h0\":\"us-east-00\",\"h1\":\"us-east-01\"}]", + "unwrap": true, + "expected": { + "normalized": "[{\"b0\":\"plain00\",\"b1\":\"plain01\",\"b2\":\"plain02\",\"h0\":\"us-east-00\",\"h1\":\"us-east-01\"}]", + "encoded": "[1]{b0,b1,b2,h0,h1}:\n plain00,plain01,plain02,\"us-east-00\",\"us-east-01\"" + } + }, + { + "name": "fine-r2-b3-h2", + "rawContent": "[{\"b0\":\"plain00\",\"b1\":\"plain01\",\"b2\":\"plain02\",\"h0\":\"us-east-00\",\"h1\":\"us-east-01\"},{\"b0\":\"plain10\",\"b1\":\"plain11\",\"b2\":\"plain12\",\"h0\":\"us-east-10\",\"h1\":\"us-east-11\"}]", + "unwrap": true, + "expected": { + "normalized": "[{\"b0\":\"plain00\",\"b1\":\"plain01\",\"b2\":\"plain02\",\"h0\":\"us-east-00\",\"h1\":\"us-east-01\"},{\"b0\":\"plain10\",\"b1\":\"plain11\",\"b2\":\"plain12\",\"h0\":\"us-east-10\",\"h1\":\"us-east-11\"}]", + "encoded": "[2]{b0,b1,b2,h0,h1}:\n plain00,plain01,plain02,\"us-east-00\",\"us-east-01\"\n plain10,plain11,plain12,\"us-east-10\",\"us-east-11\"" + } + }, + { + "name": "fine-r1-b3-h3", + "rawContent": "[{\"b0\":\"plain00\",\"b1\":\"plain01\",\"b2\":\"plain02\",\"h0\":\"us-east-00\",\"h1\":\"us-east-01\",\"h2\":\"us-east-02\"}]", + "unwrap": true, + "expected": { + "normalized": "[{\"b0\":\"plain00\",\"b1\":\"plain01\",\"b2\":\"plain02\",\"h0\":\"us-east-00\",\"h1\":\"us-east-01\",\"h2\":\"us-east-02\"}]", + "encoded": "[1]{b0,b1,b2,h0,h1,h2}:\n plain00,plain01,plain02,\"us-east-00\",\"us-east-01\",\"us-east-02\"" + } + }, + { + "name": "fine-r2-b3-h3", + "rawContent": "[{\"b0\":\"plain00\",\"b1\":\"plain01\",\"b2\":\"plain02\",\"h0\":\"us-east-00\",\"h1\":\"us-east-01\",\"h2\":\"us-east-02\"},{\"b0\":\"plain10\",\"b1\":\"plain11\",\"b2\":\"plain12\",\"h0\":\"us-east-10\",\"h1\":\"us-east-11\",\"h2\":\"us-east-12\"}]", + "unwrap": true, + "expected": { + "normalized": "[{\"b0\":\"plain00\",\"b1\":\"plain01\",\"b2\":\"plain02\",\"h0\":\"us-east-00\",\"h1\":\"us-east-01\",\"h2\":\"us-east-02\"},{\"b0\":\"plain10\",\"b1\":\"plain11\",\"b2\":\"plain12\",\"h0\":\"us-east-10\",\"h1\":\"us-east-11\",\"h2\":\"us-east-12\"}]", + "encoded": "[2]{b0,b1,b2,h0,h1,h2}:\n plain00,plain01,plain02,\"us-east-00\",\"us-east-01\",\"us-east-02\"\n plain10,plain11,plain12,\"us-east-10\",\"us-east-11\",\"us-east-12\"" + } + }, + { + "name": "fine-r1-b4-h1", + "rawContent": "[{\"b0\":\"plain00\",\"b1\":\"plain01\",\"b2\":\"plain02\",\"b3\":\"plain03\",\"h0\":\"us-east-00\"}]", + "unwrap": true, + "expected": { + "normalized": "[{\"b0\":\"plain00\",\"b1\":\"plain01\",\"b2\":\"plain02\",\"b3\":\"plain03\",\"h0\":\"us-east-00\"}]", + "encoded": "[1]{b0,b1,b2,b3,h0}:\n plain00,plain01,plain02,plain03,\"us-east-00\"" + } + }, + { + "name": "fine-r2-b4-h1", + "rawContent": "[{\"b0\":\"plain00\",\"b1\":\"plain01\",\"b2\":\"plain02\",\"b3\":\"plain03\",\"h0\":\"us-east-00\"},{\"b0\":\"plain10\",\"b1\":\"plain11\",\"b2\":\"plain12\",\"b3\":\"plain13\",\"h0\":\"us-east-10\"}]", + "unwrap": true, + "expected": { + "normalized": "[{\"b0\":\"plain00\",\"b1\":\"plain01\",\"b2\":\"plain02\",\"b3\":\"plain03\",\"h0\":\"us-east-00\"},{\"b0\":\"plain10\",\"b1\":\"plain11\",\"b2\":\"plain12\",\"b3\":\"plain13\",\"h0\":\"us-east-10\"}]", + "encoded": "[2]{b0,b1,b2,b3,h0}:\n plain00,plain01,plain02,plain03,\"us-east-00\"\n plain10,plain11,plain12,plain13,\"us-east-10\"" + } + }, + { + "name": "fine-r1-b4-h2", + "rawContent": "[{\"b0\":\"plain00\",\"b1\":\"plain01\",\"b2\":\"plain02\",\"b3\":\"plain03\",\"h0\":\"us-east-00\",\"h1\":\"us-east-01\"}]", + "unwrap": true, + "expected": { + "normalized": "[{\"b0\":\"plain00\",\"b1\":\"plain01\",\"b2\":\"plain02\",\"b3\":\"plain03\",\"h0\":\"us-east-00\",\"h1\":\"us-east-01\"}]", + "encoded": "[1]{b0,b1,b2,b3,h0,h1}:\n plain00,plain01,plain02,plain03,\"us-east-00\",\"us-east-01\"" + } + }, + { + "name": "fine-r2-b4-h2", + "rawContent": "[{\"b0\":\"plain00\",\"b1\":\"plain01\",\"b2\":\"plain02\",\"b3\":\"plain03\",\"h0\":\"us-east-00\",\"h1\":\"us-east-01\"},{\"b0\":\"plain10\",\"b1\":\"plain11\",\"b2\":\"plain12\",\"b3\":\"plain13\",\"h0\":\"us-east-10\",\"h1\":\"us-east-11\"}]", + "unwrap": true, + "expected": { + "normalized": "[{\"b0\":\"plain00\",\"b1\":\"plain01\",\"b2\":\"plain02\",\"b3\":\"plain03\",\"h0\":\"us-east-00\",\"h1\":\"us-east-01\"},{\"b0\":\"plain10\",\"b1\":\"plain11\",\"b2\":\"plain12\",\"b3\":\"plain13\",\"h0\":\"us-east-10\",\"h1\":\"us-east-11\"}]", + "encoded": "[2]{b0,b1,b2,b3,h0,h1}:\n plain00,plain01,plain02,plain03,\"us-east-00\",\"us-east-01\"\n plain10,plain11,plain12,plain13,\"us-east-10\",\"us-east-11\"" + } + }, + { + "name": "fine-r1-b4-h3", + "rawContent": "[{\"b0\":\"plain00\",\"b1\":\"plain01\",\"b2\":\"plain02\",\"b3\":\"plain03\",\"h0\":\"us-east-00\",\"h1\":\"us-east-01\",\"h2\":\"us-east-02\"}]", + "unwrap": true, + "expected": { + "normalized": "[{\"b0\":\"plain00\",\"b1\":\"plain01\",\"b2\":\"plain02\",\"b3\":\"plain03\",\"h0\":\"us-east-00\",\"h1\":\"us-east-01\",\"h2\":\"us-east-02\"}]", + "encoded": "[1]{b0,b1,b2,b3,h0,h1,h2}:\n plain00,plain01,plain02,plain03,\"us-east-00\",\"us-east-01\",\"us-east-02\"" + } + }, + { + "name": "fine-r2-b4-h3", + "rawContent": "[{\"b0\":\"plain00\",\"b1\":\"plain01\",\"b2\":\"plain02\",\"b3\":\"plain03\",\"h0\":\"us-east-00\",\"h1\":\"us-east-01\",\"h2\":\"us-east-02\"},{\"b0\":\"plain10\",\"b1\":\"plain11\",\"b2\":\"plain12\",\"b3\":\"plain13\",\"h0\":\"us-east-10\",\"h1\":\"us-east-11\",\"h2\":\"us-east-12\"}]", + "unwrap": true, + "expected": { + "normalized": "[{\"b0\":\"plain00\",\"b1\":\"plain01\",\"b2\":\"plain02\",\"b3\":\"plain03\",\"h0\":\"us-east-00\",\"h1\":\"us-east-01\",\"h2\":\"us-east-02\"},{\"b0\":\"plain10\",\"b1\":\"plain11\",\"b2\":\"plain12\",\"b3\":\"plain13\",\"h0\":\"us-east-10\",\"h1\":\"us-east-11\",\"h2\":\"us-east-12\"}]", + "encoded": "[2]{b0,b1,b2,b3,h0,h1,h2}:\n plain00,plain01,plain02,plain03,\"us-east-00\",\"us-east-01\",\"us-east-02\"\n plain10,plain11,plain12,plain13,\"us-east-10\",\"us-east-11\",\"us-east-12\"" + } + } +] diff --git a/platform/archestra-rs/proxy-transform-core/tests/golden_corpus.rs b/platform/archestra-rs/proxy-transform-core/tests/golden_corpus.rs new file mode 100644 index 00000000000..6e31e7d19e6 --- /dev/null +++ b/platform/archestra-rs/proxy-transform-core/tests/golden_corpus.rs @@ -0,0 +1,101 @@ +//! Golden-corpus conformance: every fixture input must produce exactly the +//! committed `normalized` + `encoded` output. The goldens pin the TOON spec-v3 +//! encoding the proxy ships (decision: migrated wire format, `toon-format` crate +//! is the source of truth — there is no npm oracle). +//! +//! Regenerating `tests/fixtures/golden-corpus.json`: +//! +//! 1. Inputs (only when the corpus itself changes) — from `platform/backend`: +//! `pnpm exec tsx ../archestra-rs/proxy-transform-core/tests/fixtures/gen-corpus.mts` +//! 2. Expected outputs — from `platform/archestra-rs`: +//! `UPDATE_TOON_GOLDENS=1 cargo test -p proxy_transform_core --test golden_corpus` +//! +//! Review the diff: a changed golden is a wire-format change. + +use proxy_transform_core::{ToonEncodeItem, ToonEncodeResult, toon_encode_tool_results}; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +struct GoldenCase { + name: String, + raw_content: String, + unwrap: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + expected: Option, +} + +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +struct ExpectedOutput { + normalized: String, + encoded: Option, +} + +fn fixture_path() -> std::path::PathBuf { + std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/golden-corpus.json") +} + +/// Update deliberately when the corpus generator changes — this pins fixture +/// truncation from silently shrinking coverage. +const EXPECTED_CASE_COUNT: usize = 120; + +#[test] +fn golden_corpus_conformance() { + let path = fixture_path(); + let raw = std::fs::read_to_string(&path).expect("read golden corpus fixture"); + let mut cases: Vec = serde_json::from_str(&raw).expect("parse golden corpus"); + assert_eq!( + cases.len(), + EXPECTED_CASE_COUNT, + "golden corpus size changed; update EXPECTED_CASE_COUNT deliberately" + ); + + let items: Vec = cases + .iter() + .map(|case| ToonEncodeItem { + id: case.name.clone(), + raw_content: case.raw_content.clone(), + unwrap: case.unwrap, + }) + .collect(); + let results: Vec = toon_encode_tool_results(items); + assert_eq!(results.len(), cases.len(), "positional contract"); + + if std::env::var("UPDATE_TOON_GOLDENS").as_deref() == Ok("1") { + assert!( + std::env::var_os("CI").is_none(), + "refusing to regenerate goldens in CI" + ); + for (case, result) in cases.iter_mut().zip(&results) { + case.expected = Some(ExpectedOutput { + normalized: result.normalized.clone(), + encoded: result.encoded.clone(), + }); + } + let mut out = serde_json::to_string_pretty(&cases).expect("serialize goldens"); + out.push('\n'); + std::fs::write(&path, out).expect("write golden corpus fixture"); + // Fall through: the freshly written goldens must pass the comparison. + } + + for (case, result) in cases.iter().zip(&results) { + let expected = case.expected.as_ref().unwrap_or_else(|| { + panic!( + "golden case {:?} has no expected output; regenerate with \ + UPDATE_TOON_GOLDENS=1 cargo test -p proxy_transform_core --test golden_corpus", + case.name + ) + }); + assert_eq!( + result.normalized, expected.normalized, + "normalized mismatch for {:?}", + case.name + ); + assert_eq!( + result.encoded, expected.encoded, + "encoded mismatch for {:?}", + case.name + ); + } +} diff --git a/platform/archestra-rs/proxy-transform-core/tests/roundtrip_property.rs b/platform/archestra-rs/proxy-transform-core/tests/roundtrip_property.rs new file mode 100644 index 00000000000..656b6be14b8 --- /dev/null +++ b/platform/archestra-rs/proxy-transform-core/tests/roundtrip_property.rs @@ -0,0 +1,206 @@ +//! Round-trip property tests: for generated JSON documents, +//! `decode(encode(parse(raw)))` is semantically equal to `parse(raw)`. The +//! goldens pin regression; this pins encoder/decoder semantics on shapes we did +//! not enumerate by hand. The comparison baseline is the PARSED value, not the +//! generated one: serde_json's default float parse can be 1 ULP off the +//! shortest-repr literal (a disclosed JS→Rust migration difference), and that +//! parse step is part of the kernel under test, not of TOON. +//! +//! Generator constraints mirror known `toon-format` 0.5 quirks (verified in the +//! validation spike) so we do not pin upstream bugs as our own failures: +//! - no empty objects inside containers: an array of all-empty objects encodes +//! as `[N]{}:`, which the crate's own decoder rejects (upstream issue #74); +//! - numbers stay well inside +/- 2^63 and floats moderate: integer-valued +//! floats around 2^64 and above decode back as strings; +//! - object keys containing `.` are restricted to plain dotted identifiers +//! (which the encoder emits unquoted): the decoder prepends a spurious NUL to +//! QUOTED keys containing dots (e.g. `{"a":null,"...":null}` decodes the +//! second key as `"\0..."`, and `"𝄞."`/nested `"¡."` fail the same way); +//! - in arrays that hold container elements (list layout), string elements get +//! whitespace replaced and leading digits prefixed: the encoder emits such +//! strings unquoted and its own decoder cannot re-parse the layout (e.g. +//! `["a b",[]]` and `[[],"0a"]` both fail to decode); +//! - object keys are non-empty: `[{"":null}]` encodes to a tabular header +//! `[1]{""}:` that the decoder rejects ("Field name cannot be empty"); +//! - string content limits control/whitespace characters to `\n`, `\t` and +//! space: exotic ones are emitted raw in unquoted positions and lost on +//! decode (e.g. a vertical tab in `"A\u{b} x"` decodes as `"A x"`). + +use proptest::prelude::*; +use proxy_transform_core::{ToonEncodeItem, toon_encode_tool_results}; +use serde_json::Value; + +fn arb_key() -> impl Strategy { + prop_oneof![ + // Any non-empty key without a dot (see module docs for the quirks). + "[^.]+".prop_map(neutralize_exotic_whitespace), + // Dotted identifier keys, always emitted unquoted — these round-trip. + "[a-z][a-z0-9_]{0,5}(\\.[a-z][a-z0-9_]{0,5}){1,2}", + ] +} + +fn arb_string() -> impl Strategy { + ".*".prop_map(neutralize_exotic_whitespace) +} + +/// See module docs: keep `\n`, `\t` and space (verified to round-trip), map +/// other control/whitespace characters to `_`. +fn neutralize_exotic_whitespace(text: String) -> String { + text.chars() + .map(|c| match c { + '\n' | '\t' | ' ' => c, + c if c.is_control() || c.is_whitespace() => '_', + c => c, + }) + .collect() +} + +fn arb_json() -> impl Strategy { + let leaf = prop_oneof![ + Just(Value::Null), + any::().prop_map(Value::Bool), + (-(1i64 << 62)..(1i64 << 62)).prop_map(|n| Value::Number(n.into())), + (-1.0e15..1.0e15f64).prop_map(|f| { + serde_json::Number::from_f64(f) + .map(Value::Number) + .unwrap_or(Value::Null) + }), + arb_string().prop_map(Value::String), + ]; + leaf.prop_recursive(4, 48, 8, |inner| { + prop_oneof![ + prop::collection::vec(inner.clone(), 0..8).prop_map(Value::Array), + // Objects always carry at least one key (see module docs). + prop::collection::btree_map(arb_key(), inner, 1..8) + .prop_map(|map| Value::Object(map.into_iter().collect())), + ] + }) + .prop_map(dodge_list_layout_quirk) +} + +/// Unconstrained JSON generator for encode-only properties: no decoder-quirk +/// exclusions (any strings/keys incl. control chars and dots, empty objects, +/// full i64/f64 ranges) — the wrapper property never decodes TOON. +fn arb_json_encode_only() -> impl Strategy { + let leaf = prop_oneof![ + Just(Value::Null), + any::().prop_map(Value::Bool), + any::().prop_map(|n| Value::Number(n.into())), + any::().prop_map(|f| { + serde_json::Number::from_f64(f) + .map(Value::Number) + .unwrap_or(Value::Null) + }), + ".*".prop_map(Value::String), + ]; + leaf.prop_recursive(4, 48, 8, |inner| { + prop_oneof![ + prop::collection::vec(inner.clone(), 0..8).prop_map(Value::Array), + prop::collection::btree_map(".*", inner, 0..8) + .prop_map(|map| Value::Object(map.into_iter().collect())), + ] + }) +} + +/// See module docs: keep generated shapes rich, but neutralize string elements +/// of arrays that also hold containers (whitespace, leading digits) so we do +/// not pin the encoder's unquoted-list-string / decoder disagreement as our +/// failure. +fn dodge_list_layout_quirk(value: Value) -> Value { + match value { + Value::Array(items) => { + let has_container = items.iter().any(|item| item.is_array() || item.is_object()); + let items = items + .into_iter() + .map(|item| match item { + Value::String(text) if has_container => { + let mut text = text.replace(char::is_whitespace, "_"); + if text.starts_with(|c: char| c.is_ascii_digit()) { + text.insert(0, '_'); + } + Value::String(text) + } + other => dodge_list_layout_quirk(other), + }) + .collect(); + Value::Array(items) + } + Value::Object(map) => Value::Object( + map.into_iter() + .map(|(key, item)| (key, dodge_list_layout_quirk(item))) + .collect(), + ), + other => other, + } +} + +/// Numeric-tolerant equality: i64/u64/f64 representations of the same number +/// compare equal (the decoder may pick a different `serde_json::Number` repr). +/// Integers are compared exactly (an f64 detour would collapse distinct +/// integers above 2^53); the f64 fallback only applies when a side is a float. +fn semantically_equal(a: &Value, b: &Value) -> bool { + match (a, b) { + (Value::Number(x), Value::Number(y)) => { + if let (Some(ix), Some(iy)) = (x.as_i64(), y.as_i64()) { + ix == iy + } else if let (Some(ux), Some(uy)) = (x.as_u64(), y.as_u64()) { + ux == uy + } else if let (Some(fx), Some(fy)) = (x.as_f64(), y.as_f64()) { + fx == fy + } else { + x == y + } + } + (Value::Array(xs), Value::Array(ys)) => { + xs.len() == ys.len() + && xs + .iter() + .zip(ys.iter()) + .all(|(x, y)| semantically_equal(x, y)) + } + (Value::Object(xs), Value::Object(ys)) => { + xs.len() == ys.len() + && xs + .iter() + .all(|(key, x)| ys.get(key).is_some_and(|y| semantically_equal(x, y))) + } + _ => a == b, + } +} + +proptest! { + #[test] + fn encode_decode_roundtrips(value in arb_json()) { + let raw = serde_json::to_string(&value).expect("serialize generated value"); + let parsed: Value = serde_json::from_str(&raw).expect("reparse generated document"); + let results = toon_encode_tool_results(vec![ToonEncodeItem { + id: "prop".to_string(), + raw_content: raw, + unwrap: false, + }]); + let encoded = results[0].encoded.as_ref().expect("valid JSON always encodes"); + let decoded: Value = toon_format::decode_default(encoded) + .unwrap_or_else(|error| panic!("decode failed: {error}\nencoded:\n{encoded}")); + prop_assert!( + semantically_equal(&parsed, &decoded), + "round-trip mismatch:\nparsed: {parsed}\ndecoded: {decoded}\nencoded:\n{encoded}" + ); + } + + /// Unwrapping the `[{"type":"text","text":...}]` wrapper must yield exactly + /// the encoding of the inner payload. + #[test] + fn wrapped_input_encodes_like_inner_payload(value in arb_json_encode_only()) { + let inner = serde_json::to_string(&value).expect("serialize generated value"); + let wrapped = serde_json::to_string(&serde_json::json!([ + {"type": "text", "text": inner} + ])) + .expect("serialize wrapper"); + let results = toon_encode_tool_results(vec![ + ToonEncodeItem { id: "direct".to_string(), raw_content: inner.clone(), unwrap: false }, + ToonEncodeItem { id: "wrapped".to_string(), raw_content: wrapped, unwrap: true }, + ]); + prop_assert_eq!(&results[1].normalized, &inner); + prop_assert_eq!(&results[1].encoded, &results[0].encoded); + } +} diff --git a/platform/archestra-rs/proxy-transform-rs/Cargo.toml b/platform/archestra-rs/proxy-transform-rs/Cargo.toml new file mode 100644 index 00000000000..d6abdd26ba1 --- /dev/null +++ b/platform/archestra-rs/proxy-transform-rs/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "proxy_transform_rs" +version = "0.1.0" +edition = "2024" +build = "build.rs" +publish = false + +[lib] +crate-type = ["cdylib"] + +[dependencies] +napi = "3" +napi-derive = "3" +proxy_transform_core = { path = "../proxy-transform-core", features = ["napi"] } +serde_json = "1" + +[build-dependencies] +napi-build = "2" diff --git a/platform/archestra-rs/proxy-transform-rs/build.rs b/platform/archestra-rs/proxy-transform-rs/build.rs new file mode 100644 index 00000000000..0f1b01002b0 --- /dev/null +++ b/platform/archestra-rs/proxy-transform-rs/build.rs @@ -0,0 +1,3 @@ +fn main() { + napi_build::setup(); +} diff --git a/platform/archestra-rs/proxy-transform-rs/index.cjs b/platform/archestra-rs/proxy-transform-rs/index.cjs new file mode 100644 index 00000000000..94b5b52024c --- /dev/null +++ b/platform/archestra-rs/proxy-transform-rs/index.cjs @@ -0,0 +1,16 @@ +"use strict"; + +const { loadNativeBinding, wrapAsync } = require("@archestra/napi-loader"); + +const nativeBinding = loadNativeBinding({ + dir: __dirname, + crateName: "proxy_transform_rs", + packageName: "@archestra/proxy-transform-rs", +}); + +// explicit per-name assignment so Node's cjs-module-lexer exposes each as a +// named ESM export (consumers do `import { toonEncodeToolResults } from ...`) +module.exports.toonEncodeToolResults = wrapAsync( + nativeBinding, + "toonEncodeToolResults", +); diff --git a/platform/archestra-rs/proxy-transform-rs/index.d.ts b/platform/archestra-rs/proxy-transform-rs/index.d.ts new file mode 100644 index 00000000000..ef755184a1b --- /dev/null +++ b/platform/archestra-rs/proxy-transform-rs/index.d.ts @@ -0,0 +1,34 @@ +/* auto-generated by NAPI-RS */ +/* eslint-disable */ +/** + * One tool result to transform. `id` is the provider tool id, carried for + * logging only — it is not unique across items (Anthropic reuses one + * `tool_use_id` across blocks), so results are matched to inputs by position. + */ +export interface ToonEncodeItem { + id: string + rawContent: string + unwrap: boolean +} + +/** + * The transformation output for one item. `normalized` is the unwrapped string + * when unwrapping was requested and matched, else the original `raw_content` + * (adapters tokenize it for accounting). `encoded` is the TOON encoding, or + * `None` when the content is not parseable JSON. + * + * `use_nullable` makes `encoded: None` cross the boundary as an explicit JS + * `null` (typed `string | null`) instead of an omitted key. + */ +export interface ToonEncodeResult { + normalized: string + encoded: string | null +} +/** + * Transform a batch of tool results off the JS thread: optionally unwrap the + * `[{"type":"text","text":...}]` client wrapper, parse the JSON, and encode it + * as TOON (spec v3). Results are positional — same length and order as + * `items`; content that is not parseable JSON yields `encoded: null` (the + * caller keeps the original payload). + */ +export declare function toonEncodeToolResults(items: Array): Promise> diff --git a/platform/archestra-rs/proxy-transform-rs/package.json b/platform/archestra-rs/proxy-transform-rs/package.json new file mode 100644 index 00000000000..ca325e2166e --- /dev/null +++ b/platform/archestra-rs/proxy-transform-rs/package.json @@ -0,0 +1,30 @@ +{ + "name": "@archestra/proxy-transform-rs", + "version": "0.1.0", + "private": true, + "type": "commonjs", + "main": "index.cjs", + "types": "index.d.ts", + "scripts": { + "build": "napi build --release --platform", + "build:dev": "napi build --profile release-fast --platform", + "check:ci": "cargo fmt --check --all && cargo check -p proxy_transform_core -p proxy_transform_rs --locked && cargo clippy -p proxy_transform_core -p proxy_transform_rs --all-targets --locked -- -D warnings && cargo test -p proxy_transform_core --locked && cargo test -p proxy_transform_core --features napi --locked && pnpm build && pnpm smoke && pnpm smoke:esm", + "check:musl": "cargo test -p proxy_transform_core --locked && pnpm build && pnpm smoke && pnpm smoke:esm", + "lint": "cargo fmt --check --all", + "smoke": "node smoke.test.cjs", + "smoke:esm": "node smoke.esm.test.mjs", + "test": "cargo test -p proxy_transform_core --locked", + "type-check": "cargo check -p proxy_transform_core -p proxy_transform_rs --locked" + }, + "dependencies": { + "@archestra/napi-loader": "workspace:*" + }, + "devDependencies": { + "@napi-rs/cli": "^3.4.0" + }, + "files": [ + "index.cjs", + "index.d.ts", + "*.node" + ] +} diff --git a/platform/archestra-rs/proxy-transform-rs/smoke.esm.test.mjs b/platform/archestra-rs/proxy-transform-rs/smoke.esm.test.mjs new file mode 100644 index 00000000000..11ee76cf36f --- /dev/null +++ b/platform/archestra-rs/proxy-transform-rs/smoke.esm.test.mjs @@ -0,0 +1,18 @@ +// The backend reaches this addon through an ESM dynamic `import()` and a named +// destructure (`const { toonEncodeToolResults } = await import(...)`). The CJS +// smoke (`require`) does not exercise that interop path, so this mirrors it: +// the named export must be exposed to ESM (via cjs-module-lexer) and callable. +import assert from "node:assert/strict"; + +const { toonEncodeToolResults } = await import("./index.cjs"); + +assert.equal(typeof toonEncodeToolResults, "function"); + +// The async binding is reachable via ESM interop and resolves positionally +// (null encoding for non-JSON content). +const results = await toonEncodeToolResults([ + { id: "esm", rawContent: "not json", unwrap: true }, +]); +assert.deepEqual(results, [{ normalized: "not json", encoded: null }]); + +console.log("proxy-transform-rs esm smoke ok"); diff --git a/platform/archestra-rs/proxy-transform-rs/smoke.test.cjs b/platform/archestra-rs/proxy-transform-rs/smoke.test.cjs new file mode 100644 index 00000000000..64dfd57547e --- /dev/null +++ b/platform/archestra-rs/proxy-transform-rs/smoke.test.cjs @@ -0,0 +1,39 @@ +"use strict"; + +const assert = require("node:assert/strict"); +const proxyTransform = require("./index.cjs"); + +// toonEncodeToolResults: a tiny batch resolves off-thread, positionally, with +// TOON output for JSON content, unwrapping applied when requested, and +// `encoded: null` for non-JSON. Heavy correctness lives in the +// proxy_transform_core cargo tests (goldens + property tests). +const inner = JSON.stringify({ data: [{ id: 1, v: "a" }, { id: 2, v: "b" }], ok: true }); +const items = [ + { id: "plain", rawContent: '{"a":1}', unwrap: false }, + { + id: "wrapped", + rawContent: JSON.stringify([{ type: "text", text: inner }]), + unwrap: true, + }, + { id: "malformed", rawContent: "not json at all", unwrap: true }, +]; + +(async () => { + const results = await proxyTransform.toonEncodeToolResults(items); + assert.equal(results.length, items.length); + + assert.deepEqual(results[0], { normalized: '{"a":1}', encoded: "a: 1" }); + + assert.equal(results[1].normalized, inner); + assert.equal(results[1].encoded, "data[2]{id,v}:\n 1,a\n 2,b\nok: true"); + + assert.deepEqual(results[2], { normalized: "not json at all", encoded: null }); + + // Empty batch resolves to an empty array. + assert.deepEqual(await proxyTransform.toonEncodeToolResults([]), []); + + console.log("proxy-transform-rs smoke ok"); +})().catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/platform/archestra-rs/proxy-transform-rs/src/lib.rs b/platform/archestra-rs/proxy-transform-rs/src/lib.rs new file mode 100644 index 00000000000..91a7f46facf --- /dev/null +++ b/platform/archestra-rs/proxy-transform-rs/src/lib.rs @@ -0,0 +1,71 @@ +//! Thin NAPI adapter over `proxy_transform_core`. Receives the tool-result batch +//! as owned Rust data on the JS thread, offloads the unwrap/parse/TOON-encode +//! work to the libuv threadpool, and converts a panic into a structured JS +//! error. No product logic lives here — deleting this layer must not delete the +//! core logic. The `#[napi(object)]` DTO shapes live in the core behind its +//! `napi` feature (sandbox-core pattern), so the generated `index.d.ts` mirrors +//! the core types exactly. + +use std::any::Any; + +use napi::bindgen_prelude::AsyncTask; +use napi::{Env, Task}; +use napi_derive::napi; + +use proxy_transform_core as core; + +/// Unwrap/parse/TOON-encode work for one batch of tool results, run on the libuv +/// threadpool so the JS event loop is never blocked by a large payload. The +/// items are converted to owned Rust data on the JS thread before `compute` +/// runs, so nothing here touches a JS handle. +pub struct ToonEncodeTask { + items: Vec, +} + +impl Task for ToonEncodeTask { + type Output = Vec; + type JsValue = Vec; + + fn compute(&mut self) -> napi::Result { + let items = std::mem::take(&mut self.items); + std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + core::toon_encode_tool_results(items) + })) + .map_err(panic_to_napi_error) + } + + fn resolve(&mut self, _env: Env, output: Self::Output) -> napi::Result { + Ok(output) + } +} + +/// Transform a batch of tool results off the JS thread: optionally unwrap the +/// `[{"type":"text","text":...}]` client wrapper, parse the JSON, and encode it +/// as TOON (spec v3). Results are positional — same length and order as +/// `items`; content that is not parseable JSON yields `encoded: null` (the +/// caller keeps the original payload). +#[napi( + js_name = "toonEncodeToolResults", + ts_return_type = "Promise>" +)] +pub fn toon_encode_tool_results(items: Vec) -> AsyncTask { + AsyncTask::new(ToonEncodeTask { items }) +} + +fn panic_to_napi_error(payload: Box) -> napi::Error { + let body = serde_json::json!({ + "code": "ARCHESTRA_INTERNAL", + "message": format!("rust panic: {}", panic_payload_message(payload.as_ref())), + }); + napi::Error::new(napi::Status::GenericFailure, body.to_string()) +} + +fn panic_payload_message(payload: &(dyn Any + Send)) -> &str { + if let Some(s) = payload.downcast_ref::<&'static str>() { + return s; + } + if let Some(s) = payload.downcast_ref::() { + return s.as_str(); + } + "unknown panic payload" +} diff --git a/platform/pnpm-lock.yaml b/platform/pnpm-lock.yaml index b5ba94cd369..673ee9bbbde 100644 --- a/platform/pnpm-lock.yaml +++ b/platform/pnpm-lock.yaml @@ -129,6 +129,16 @@ importers: archestra-rs/napi-loader: {} + archestra-rs/proxy-transform-rs: + dependencies: + '@archestra/napi-loader': + specifier: workspace:* + version: link:../napi-loader + devDependencies: + '@napi-rs/cli': + specifier: ^3.4.0 + version: 3.6.2(@emnapi/runtime@1.8.1)(@types/node@25.9.1) + archestra-rs/sandbox-rs: dependencies: '@archestra/napi-loader': diff --git a/platform/pnpm-workspace.yaml b/platform/pnpm-workspace.yaml index 104a1c35b91..118262b772d 100644 --- a/platform/pnpm-workspace.yaml +++ b/platform/pnpm-workspace.yaml @@ -7,6 +7,7 @@ packages: - "archestra-rs/sandbox-rs" - "archestra-rs/app-runtime-rs" - "archestra-rs/image-rs" + - "archestra-rs/proxy-transform-rs" - "e2e-tests" catalog: From 43b3af9dea08bbaa2cb95738b7afb9809d30ba0b Mon Sep 17 00:00:00 2001 From: Arseny Kravchenko Date: Fri, 10 Jul 2026 11:38:51 +0200 Subject: [PATCH 04/18] feat(proxy): native TOON kernel cutover for the OpenAI adapter Add routes/proxy/utils/toon-native.ts: one batched call per request into @archestra/proxy-transform-rs (AsyncTask on the libuv pool), positional results with a hard length assert, fail-open to uncompressed on any load/call failure, and an eager startup probe wired into both web and worker entry modes with an error log and llm_toon_addon_load_failures_total{context} metric. Compression skipped due to addon failure is now reported honestly as toonSkipReason "addon_unavailable" end to end: stats contract, handler precedence, session-count SQL aggregation, Savings UI branch, mocks, and regenerated OpenAPI/client types. The OpenAI adapter keeps its keep/reject and accounting semantics exactly (tokenizes the unwrapped string, strict fewer-tokens rule, rejected payloads counted in both totals); TOON bytes now come from the Rust kernel (toon-format 0.5.0). Tests: 120-case golden gate (fails in CI without the addon, skips visibly locally), boundary mocks, exact transformed-request equality incl. interleaved non-candidate messages, stats matrix, handler-level skip reason; provider-matrix TOON assertions pass unchanged. Claude-Session: https://claude.ai/code/session_01QkeLJprfeq75d2NY39aFiu --- docs/openapi.json | 138 +++++--- platform/backend/knip.config.ts | 1 + platform/backend/package.json | 2 + platform/backend/src/models/interaction.ts | 2 + .../backend/src/observability/metrics/llm.ts | 21 ++ .../adapters/openai-toon-compression.test.ts | 310 ++++++++++++++++++ .../src/routes/proxy/adapters/openai.ts | 193 ++++++----- .../src/routes/proxy/llm-proxy-handler.ts | 6 +- .../routes/toon-addon-unavailable.test.ts | 147 +++++++++ .../proxy/utils/toon-native.golden.test.ts | 74 +++++ .../routes/proxy/utils/toon-native.test.ts | 120 +++++++ .../src/routes/proxy/utils/toon-native.ts | 72 ++++ platform/backend/src/server.ts | 9 + platform/backend/src/types/interaction.ts | 1 + .../src/types/tool-result-compression.ts | 8 + platform/frontend/src/components/savings.tsx | 5 + .../frontend/src/mocks/data/interactions.ts | 1 + platform/pnpm-lock.yaml | 3 + .../shared/hey-api/clients/api/types.gen.ts | 89 ++--- 19 files changed, 1032 insertions(+), 170 deletions(-) create mode 100644 platform/backend/src/routes/proxy/adapters/openai-toon-compression.test.ts create mode 100644 platform/backend/src/routes/proxy/routes/toon-addon-unavailable.test.ts create mode 100644 platform/backend/src/routes/proxy/utils/toon-native.golden.test.ts create mode 100644 platform/backend/src/routes/proxy/utils/toon-native.test.ts create mode 100644 platform/backend/src/routes/proxy/utils/toon-native.ts diff --git a/docs/openapi.json b/docs/openapi.json index e52bcefcfc5..3dcbdf7b025 100644 --- a/docs/openapi.json +++ b/docs/openapi.json @@ -111917,7 +111917,8 @@ "enum": [ "not_enabled", "not_effective", - "no_tool_results" + "no_tool_results", + "addon_unavailable" ] }, "createdAt": { @@ -112880,7 +112881,8 @@ "enum": [ "not_enabled", "not_effective", - "no_tool_results" + "no_tool_results", + "addon_unavailable" ] }, "createdAt": { @@ -113551,7 +113553,8 @@ "enum": [ "not_enabled", "not_effective", - "no_tool_results" + "no_tool_results", + "addon_unavailable" ] }, "createdAt": { @@ -114211,7 +114214,8 @@ "enum": [ "not_enabled", "not_effective", - "no_tool_results" + "no_tool_results", + "addon_unavailable" ] }, "createdAt": { @@ -114742,7 +114746,8 @@ "enum": [ "not_enabled", "not_effective", - "no_tool_results" + "no_tool_results", + "addon_unavailable" ] }, "createdAt": { @@ -115284,7 +115289,8 @@ "enum": [ "not_enabled", "not_effective", - "no_tool_results" + "no_tool_results", + "addon_unavailable" ] }, "createdAt": { @@ -117777,7 +117783,8 @@ "enum": [ "not_enabled", "not_effective", - "no_tool_results" + "no_tool_results", + "addon_unavailable" ] }, "createdAt": { @@ -118319,7 +118326,8 @@ "enum": [ "not_enabled", "not_effective", - "no_tool_results" + "no_tool_results", + "addon_unavailable" ] }, "createdAt": { @@ -118861,7 +118869,8 @@ "enum": [ "not_enabled", "not_effective", - "no_tool_results" + "no_tool_results", + "addon_unavailable" ] }, "createdAt": { @@ -119403,7 +119412,8 @@ "enum": [ "not_enabled", "not_effective", - "no_tool_results" + "no_tool_results", + "addon_unavailable" ] }, "createdAt": { @@ -119945,7 +119955,8 @@ "enum": [ "not_enabled", "not_effective", - "no_tool_results" + "no_tool_results", + "addon_unavailable" ] }, "createdAt": { @@ -120487,7 +120498,8 @@ "enum": [ "not_enabled", "not_effective", - "no_tool_results" + "no_tool_results", + "addon_unavailable" ] }, "createdAt": { @@ -121029,7 +121041,8 @@ "enum": [ "not_enabled", "not_effective", - "no_tool_results" + "no_tool_results", + "addon_unavailable" ] }, "createdAt": { @@ -121571,7 +121584,8 @@ "enum": [ "not_enabled", "not_effective", - "no_tool_results" + "no_tool_results", + "addon_unavailable" ] }, "createdAt": { @@ -122102,7 +122116,8 @@ "enum": [ "not_enabled", "not_effective", - "no_tool_results" + "no_tool_results", + "addon_unavailable" ] }, "createdAt": { @@ -122633,7 +122648,8 @@ "enum": [ "not_enabled", "not_effective", - "no_tool_results" + "no_tool_results", + "addon_unavailable" ] }, "createdAt": { @@ -123175,7 +123191,8 @@ "enum": [ "not_enabled", "not_effective", - "no_tool_results" + "no_tool_results", + "addon_unavailable" ] }, "createdAt": { @@ -123717,7 +123734,8 @@ "enum": [ "not_enabled", "not_effective", - "no_tool_results" + "no_tool_results", + "addon_unavailable" ] }, "createdAt": { @@ -126430,7 +126448,8 @@ "enum": [ "not_enabled", "not_effective", - "no_tool_results" + "no_tool_results", + "addon_unavailable" ] }, "createdAt": { @@ -126972,7 +126991,8 @@ "enum": [ "not_enabled", "not_effective", - "no_tool_results" + "no_tool_results", + "addon_unavailable" ] }, "createdAt": { @@ -127748,7 +127768,8 @@ "enum": [ "not_enabled", "not_effective", - "no_tool_results" + "no_tool_results", + "addon_unavailable" ] }, "createdAt": { @@ -128711,7 +128732,8 @@ "enum": [ "not_enabled", "not_effective", - "no_tool_results" + "no_tool_results", + "addon_unavailable" ] }, "createdAt": { @@ -129447,13 +129469,17 @@ }, "noToolResults": { "type": "number" + }, + "addonUnavailable": { + "type": "number" } }, "required": [ "applied", "notEnabled", "notEffective", - "noToolResults" + "noToolResults", + "addonUnavailable" ], "additionalProperties": false }, @@ -130755,7 +130781,8 @@ "enum": [ "not_enabled", "not_effective", - "no_tool_results" + "no_tool_results", + "addon_unavailable" ] }, "createdAt": { @@ -131718,7 +131745,8 @@ "enum": [ "not_enabled", "not_effective", - "no_tool_results" + "no_tool_results", + "addon_unavailable" ] }, "createdAt": { @@ -132389,7 +132417,8 @@ "enum": [ "not_enabled", "not_effective", - "no_tool_results" + "no_tool_results", + "addon_unavailable" ] }, "createdAt": { @@ -133049,7 +133078,8 @@ "enum": [ "not_enabled", "not_effective", - "no_tool_results" + "no_tool_results", + "addon_unavailable" ] }, "createdAt": { @@ -133580,7 +133610,8 @@ "enum": [ "not_enabled", "not_effective", - "no_tool_results" + "no_tool_results", + "addon_unavailable" ] }, "createdAt": { @@ -134122,7 +134153,8 @@ "enum": [ "not_enabled", "not_effective", - "no_tool_results" + "no_tool_results", + "addon_unavailable" ] }, "createdAt": { @@ -136615,7 +136647,8 @@ "enum": [ "not_enabled", "not_effective", - "no_tool_results" + "no_tool_results", + "addon_unavailable" ] }, "createdAt": { @@ -137157,7 +137190,8 @@ "enum": [ "not_enabled", "not_effective", - "no_tool_results" + "no_tool_results", + "addon_unavailable" ] }, "createdAt": { @@ -137699,7 +137733,8 @@ "enum": [ "not_enabled", "not_effective", - "no_tool_results" + "no_tool_results", + "addon_unavailable" ] }, "createdAt": { @@ -138241,7 +138276,8 @@ "enum": [ "not_enabled", "not_effective", - "no_tool_results" + "no_tool_results", + "addon_unavailable" ] }, "createdAt": { @@ -138783,7 +138819,8 @@ "enum": [ "not_enabled", "not_effective", - "no_tool_results" + "no_tool_results", + "addon_unavailable" ] }, "createdAt": { @@ -139325,7 +139362,8 @@ "enum": [ "not_enabled", "not_effective", - "no_tool_results" + "no_tool_results", + "addon_unavailable" ] }, "createdAt": { @@ -139867,7 +139905,8 @@ "enum": [ "not_enabled", "not_effective", - "no_tool_results" + "no_tool_results", + "addon_unavailable" ] }, "createdAt": { @@ -140409,7 +140448,8 @@ "enum": [ "not_enabled", "not_effective", - "no_tool_results" + "no_tool_results", + "addon_unavailable" ] }, "createdAt": { @@ -140940,7 +140980,8 @@ "enum": [ "not_enabled", "not_effective", - "no_tool_results" + "no_tool_results", + "addon_unavailable" ] }, "createdAt": { @@ -141471,7 +141512,8 @@ "enum": [ "not_enabled", "not_effective", - "no_tool_results" + "no_tool_results", + "addon_unavailable" ] }, "createdAt": { @@ -142013,7 +142055,8 @@ "enum": [ "not_enabled", "not_effective", - "no_tool_results" + "no_tool_results", + "addon_unavailable" ] }, "createdAt": { @@ -142555,7 +142598,8 @@ "enum": [ "not_enabled", "not_effective", - "no_tool_results" + "no_tool_results", + "addon_unavailable" ] }, "createdAt": { @@ -145268,7 +145312,8 @@ "enum": [ "not_enabled", "not_effective", - "no_tool_results" + "no_tool_results", + "addon_unavailable" ] }, "createdAt": { @@ -145810,7 +145855,8 @@ "enum": [ "not_enabled", "not_effective", - "no_tool_results" + "no_tool_results", + "addon_unavailable" ] }, "createdAt": { @@ -146586,7 +146632,8 @@ "enum": [ "not_enabled", "not_effective", - "no_tool_results" + "no_tool_results", + "addon_unavailable" ] }, "createdAt": { @@ -147549,7 +147596,8 @@ "enum": [ "not_enabled", "not_effective", - "no_tool_results" + "no_tool_results", + "addon_unavailable" ] }, "createdAt": { diff --git a/platform/backend/knip.config.ts b/platform/backend/knip.config.ts index 2e47fc61d42..074831a63cd 100644 --- a/platform/backend/knip.config.ts +++ b/platform/backend/knip.config.ts @@ -24,6 +24,7 @@ const config: KnipConfig = { "@archestra/sandbox-rs", "@archestra/app-runtime-rs", "@archestra/image-rs", + "@archestra/proxy-transform-rs", ], ignoreBinaries: [ // biome and concurrently are in root package.json diff --git a/platform/backend/package.json b/platform/backend/package.json index f2cd8dfe194..f641cdd172d 100644 --- a/platform/backend/package.json +++ b/platform/backend/package.json @@ -19,6 +19,7 @@ "dev": "tsdown --watch", "dev:debug": "DEBUG=1 tsdown --watch", "test": "vitest", + "test:native": "pnpm --filter @archestra/proxy-transform-rs build && vitest run src/routes/proxy/utils/toon-native.golden.test.ts src/routes/proxy/adapters/openai-toon-compression.test.ts src/routes/proxy/routes/provider-matrix.test.ts", "lint": "biome check", "lint:fix": "biome check --write", "lint:fix:unsafe": "biome check --write --unsafe", @@ -58,6 +59,7 @@ "@anthropic-ai/tokenizer": "^0.0.4", "@archestra/app-runtime-rs": "workspace:*", "@archestra/image-rs": "workspace:*", + "@archestra/proxy-transform-rs": "workspace:*", "@archestra/sandbox-rs": "workspace:*", "@archestra/shared": "workspace:*", "@aws-crypto/sha256-js": "^5.2.0", diff --git a/platform/backend/src/models/interaction.ts b/platform/backend/src/models/interaction.ts index 3f7cdbbcb49..440b7efa222 100644 --- a/platform/backend/src/models/interaction.ts +++ b/platform/backend/src/models/interaction.ts @@ -1087,6 +1087,7 @@ class InteractionModel { toonNotEnabledCount: sql`COUNT(*) FILTER (WHERE ${schema.interactionsTable.toonSkipReason} = 'not_enabled')`, toonNotEffectiveCount: sql`COUNT(*) FILTER (WHERE ${schema.interactionsTable.toonSkipReason} = 'not_effective')`, toonNoToolResultsCount: sql`COUNT(*) FILTER (WHERE ${schema.interactionsTable.toonSkipReason} = 'no_tool_results')`, + toonAddonUnavailableCount: sql`COUNT(*) FILTER (WHERE ${schema.interactionsTable.toonSkipReason} = 'addon_unavailable')`, firstRequestTime: min(schema.interactionsTable.createdAt), lastRequestTime: max(schema.interactionsTable.createdAt), models: sql`STRING_AGG(DISTINCT ${schema.interactionsTable.model}, ',')`, @@ -1188,6 +1189,7 @@ class InteractionModel { notEnabled: Number(s.toonNotEnabledCount) || 0, notEffective: Number(s.toonNotEffectiveCount) || 0, noToolResults: Number(s.toonNoToolResultsCount) || 0, + addonUnavailable: Number(s.toonAddonUnavailableCount) || 0, }, firstRequestTime: s.firstRequestTime ?? new Date(), lastRequestTime: s.lastRequestTime ?? new Date(), diff --git a/platform/backend/src/observability/metrics/llm.ts b/platform/backend/src/observability/metrics/llm.ts index d5aa4656a94..a86fde01404 100644 --- a/platform/backend/src/observability/metrics/llm.ts +++ b/platform/backend/src/observability/metrics/llm.ts @@ -840,6 +840,27 @@ export function reportKbLlmCall(params: { } } +// Lazily registered instead of inside initializeMetrics(): it has no dynamic +// agent labels, must never be reset when label keys change, and load failures +// can occur before metrics initialization (the startup probe). +let llmToonAddonLoadFailures: client.Counter | undefined; + +/** + * Reports a failure to load or call the native proxy-transform addon. + * TOON tool-result compression fails open (skipped) when this fires, with the + * `addon_unavailable` skip reason persisted on the interaction. + */ +export function reportToonAddonUnavailable( + context: "startup" | "request", +): void { + llmToonAddonLoadFailures ??= new client.Counter({ + name: "llm_toon_addon_load_failures_total", + help: "Failures to load or call the native proxy-transform addon (TOON compression fails open and is skipped)", + labelNames: ["context"], + }); + llmToonAddonLoadFailures.inc({ context }); +} + function extractHeaderNames( headers: HeadersInit | undefined, ): Record { diff --git a/platform/backend/src/routes/proxy/adapters/openai-toon-compression.test.ts b/platform/backend/src/routes/proxy/adapters/openai-toon-compression.test.ts new file mode 100644 index 00000000000..09b43db0d84 --- /dev/null +++ b/platform/backend/src/routes/proxy/adapters/openai-toon-compression.test.ts @@ -0,0 +1,310 @@ +// Pins the OpenAI adapter's TOON compression cutover to the native addon: +// full transformed-request exact equality (TOON content from the committed v3 +// golden corpus, everything else byte-equal) and the exact +// ToolCompressionStats accounting matrix (rejected payloads counted in BOTH +// totals — the OpenAI-family rule). Requires the built addon: mandatory in +// CI; locally it skips visibly — run `pnpm test:native` from platform/backend. + +import { readFileSync } from "node:fs"; +import path from "node:path"; +import { ModelModel } from "@/models"; +import { describe, expect, test } from "@/test"; +import { getTokenizer } from "@/tokenizers"; +import type { OpenAi } from "@/types"; +import { openaiAdapterFactory } from "./openai"; + +type OpenAiRequest = OpenAi.Types.ChatCompletionsRequest; + +type GoldenEntry = { + name: string; + rawContent: string; + unwrap: boolean; + expected: { normalized: string; encoded: string | null }; +}; + +const CORPUS_PATH = path.resolve( + import.meta.dirname, + "../../../../../archestra-rs/proxy-transform-core/tests/fixtures/golden-corpus.json", +); +const corpus: GoldenEntry[] = JSON.parse(readFileSync(CORPUS_PATH, "utf8")); +function corpusEntry(name: string): GoldenEntry { + const entry = corpus.find((candidate) => candidate.name === name); + if (!entry) throw new Error(`golden corpus entry not found: ${name}`); + return entry; +} + +// Corpus picks (see the corpus for content): a uniform array (compression +// wins), a wrapped [{type:"text",...}] payload (unwrapped, compression wins), +// malformed JSON (kept as-is), and a near-boundary object whose TOON encoding +// does not save tokens under the OpenAI tokenizer (rejected). +const UNIFORM = corpusEntry("provider-matrix-tool-result"); +const WRAPPED = corpusEntry("wrapped-single-text"); +const MALFORMED = corpusEntry("malformed-prose"); +const NEAR_BOUNDARY = corpusEntry("boundary-obj-1"); + +const tokenizer = getTokenizer("openai"); +const countTokens = (content: string) => + tokenizer.countTokens([{ role: "user", content }]); + +// $1,000,000 per million input tokens = $1 per token, so expected costSavings +// equals tokens saved exactly. +async function upsertOneDollarPerTokenPricing() { + await ModelModel.upsert({ + externalId: "openai/gpt-4o", + provider: "openai", + modelId: "gpt-4o", + inputModalities: null, + outputModalities: null, + customPricePerMillionInput: "1000000.00", + customPricePerMillionOutput: "1000000.00", + lastSyncedAt: new Date(), + }); +} + +const addonLoadError: unknown = await import( + "@archestra/proxy-transform-rs" +).then( + () => null, + (error) => error, +); + +// In CI the suite must FAIL (never skip) when the addon is missing. +const describeNative = + addonLoadError === null || process.env.CI ? describe : describe.skip; +if (addonLoadError !== null && !process.env.CI) { + console.warn( + `[openai-toon-compression.test] skipping: @archestra/proxy-transform-rs is not built (${String( + addonLoadError, + )}). Run \`pnpm test:native\` from platform/backend.`, + ); +} + +function makeToolCall(id: string, name: string) { + return { + id, + type: "function" as const, + function: { name, arguments: '{"directory":"."}' }, + }; +} + +describeNative("OpenAI adapter TOON compression (native addon)", () => { + test("transforms the full provider request exactly (goldens for TOON, byte-equal elsewhere)", async () => { + await upsertOneDollarPerTokenPricing(); + + const makeRequest = (): OpenAiRequest => ({ + model: "gpt-4o", + temperature: 0.25, + messages: [ + { role: "system", content: "You are a filesystem assistant." }, + { role: "user", content: "What files are in the current directory?" }, + { + role: "assistant", + content: null, + tool_calls: [ + makeToolCall("call_uniform", "list_files"), + makeToolCall("call_wrapped", "read_wrapped"), + makeToolCall("call_malformed", "read_notes"), + makeToolCall("call_boundary", "read_config"), + ], + }, + { + role: "tool", + tool_call_id: "call_uniform", + content: UNIFORM.rawContent, + }, + { + role: "tool", + tool_call_id: "call_wrapped", + content: WRAPPED.rawContent, + }, + { + role: "tool", + tool_call_id: "call_malformed", + content: MALFORMED.rawContent, + }, + { + role: "tool", + tool_call_id: "call_boundary", + content: NEAR_BOUNDARY.rawContent, + }, + ], + }); + + const adapter = openaiAdapterFactory.createRequestAdapter(makeRequest()); + const stats = await adapter.applyToonCompression("gpt-4o"); + + const expectedRequest = makeRequest(); + // Compression wins for the uniform array and the wrapped payload; the + // malformed and near-boundary results keep their original content. + expectedRequest.messages[3] = { + role: "tool", + tool_call_id: "call_uniform", + content: UNIFORM.expected.encoded as string, + }; + expectedRequest.messages[4] = { + role: "tool", + tool_call_id: "call_wrapped", + content: WRAPPED.expected.encoded as string, + }; + expect(adapter.toProviderRequest()).toStrictEqual(expectedRequest); + + // Rejected payloads count their original tokens in BOTH totals; malformed + // content is not counted at all. + const boundaryTokens = countTokens(NEAR_BOUNDARY.expected.normalized); + const tokensBefore = + countTokens(UNIFORM.expected.normalized) + + countTokens(WRAPPED.expected.normalized) + + boundaryTokens; + const tokensAfter = + countTokens(UNIFORM.expected.encoded as string) + + countTokens(WRAPPED.expected.encoded as string) + + boundaryTokens; + expect(stats).toStrictEqual({ + tokensBefore, + tokensAfter, + costSavings: tokensBefore - tokensAfter, + wasEffective: true, + hadToolResults: true, + }); + }); + + test("applies native results to the right candidates when non-string tool messages are interleaved", async () => { + await upsertOneDollarPerTokenPricing(); + + // Non-string (array-content) tool messages are NOT candidates for the + // native batch. Interleaving them between string candidates means the + // native result index diverges from both the message index and the + // tool-message index — off-by-one positional application would compress + // the wrong message and fail the full-request equality below. + const makeRequest = (): OpenAiRequest => ({ + model: "gpt-4o", + messages: [ + { role: "user", content: "Inspect the workspace." }, + { + role: "assistant", + content: null, + tool_calls: [ + makeToolCall("call_a", "read_config"), + makeToolCall("call_block1", "read_chunks"), + makeToolCall("call_b", "list_files"), + makeToolCall("call_block2", "read_more_chunks"), + makeToolCall("call_c", "read_notes"), + ], + }, + { + role: "tool", + tool_call_id: "call_a", + content: NEAR_BOUNDARY.rawContent, + }, + { + role: "tool", + tool_call_id: "call_block1", + content: [{ type: "text", text: "chunk one" }], + }, + { + role: "tool", + tool_call_id: "call_b", + content: UNIFORM.rawContent, + }, + { + role: "tool", + tool_call_id: "call_block2", + content: [{ type: "text", text: "chunk two" }], + }, + { + role: "tool", + tool_call_id: "call_c", + content: MALFORMED.rawContent, + }, + ], + }); + + const adapter = openaiAdapterFactory.createRequestAdapter(makeRequest()); + const stats = await adapter.applyToonCompression("gpt-4o"); + + // Only B compresses; A is rejected (near-boundary), C is malformed, and + // the two array-content messages are untouched. + const expectedRequest = makeRequest(); + expectedRequest.messages[4] = { + role: "tool", + tool_call_id: "call_b", + content: UNIFORM.expected.encoded as string, + }; + expect(adapter.toProviderRequest()).toStrictEqual(expectedRequest); + + const boundaryTokens = countTokens(NEAR_BOUNDARY.expected.normalized); + const tokensBefore = + boundaryTokens + countTokens(UNIFORM.expected.normalized); + const tokensAfter = + boundaryTokens + countTokens(UNIFORM.expected.encoded as string); + expect(stats).toStrictEqual({ + tokensBefore, + tokensAfter, + costSavings: tokensBefore - tokensAfter, + wasEffective: true, + hadToolResults: true, + }); + }); + + describe("exact ToolCompressionStats accounting matrix", () => { + const rows: { + row: string; + entry: GoldenEntry; + compressed: boolean; + counted: boolean; + }[] = [ + { row: "malformed", entry: MALFORMED, compressed: false, counted: false }, + { + row: "ineffective (rejected: original counted in both totals)", + entry: NEAR_BOUNDARY, + compressed: false, + counted: true, + }, + { row: "effective", entry: UNIFORM, compressed: true, counted: true }, + { + row: "wrapped-array", + entry: WRAPPED, + compressed: true, + counted: true, + }, + ]; + + for (const { row, entry, compressed, counted } of rows) { + test(row, async () => { + await upsertOneDollarPerTokenPricing(); + + const adapter = openaiAdapterFactory.createRequestAdapter({ + model: "gpt-4o", + messages: [ + { role: "user", content: "run the tool" }, + { role: "tool", tool_call_id: "call_1", content: entry.rawContent }, + ], + }); + const stats = await adapter.applyToonCompression("gpt-4o"); + + const tokensBefore = counted + ? countTokens(entry.expected.normalized) + : 0; + const tokensAfter = compressed + ? countTokens(entry.expected.encoded as string) + : tokensBefore; + expect(stats).toStrictEqual({ + tokensBefore, + tokensAfter, + costSavings: tokensBefore - tokensAfter, + wasEffective: compressed, + hadToolResults: counted, + }); + + const [, toolMessage] = adapter.getProviderMessages(); + expect(toolMessage).toStrictEqual({ + role: "tool", + tool_call_id: "call_1", + content: compressed + ? (entry.expected.encoded as string) + : entry.rawContent, + }); + }); + } + }); +}); diff --git a/platform/backend/src/routes/proxy/adapters/openai.ts b/platform/backend/src/routes/proxy/adapters/openai.ts index ea8ed90f242..8ee01d98dbd 100644 --- a/platform/backend/src/routes/proxy/adapters/openai.ts +++ b/platform/backend/src/routes/proxy/adapters/openai.ts @@ -3,7 +3,6 @@ import { ArchestraInternalErrorCode, type SupportedProvider, } from "@archestra/shared"; -import { encode as toonEncode } from "@toon-format/toon"; import { get } from "lodash-es"; import OpenAIProvider from "openai"; import type { @@ -44,7 +43,7 @@ import { isMcpImageBlock, } from "../utils/mcp-image"; import { stripBrowserToolsResults } from "../utils/summarize-tool-results"; -import { unwrapToolContent } from "../utils/unwrap-tool-content"; +import { toonEncodeToolResults } from "../utils/toon-native"; // ============================================================================= // TYPE ALIASES @@ -1276,7 +1275,16 @@ export async function convertToolResultsToToon( let totalTokensBefore = 0; let totalTokensAfter = 0; - const result = messages.map((message) => { + // Collect candidate tool messages first so the native unwrap→parse→encode + // transform runs once per request (batched, off the JS thread); results are + // positional and reapplied by message index. + type OpenAiToolMessage = Extract; + const candidates: { + index: number; + message: OpenAiToolMessage; + content: string; + }[] = []; + messages.forEach((message, index) => { if (message.role === "tool") { logger.info( { @@ -1288,86 +1296,111 @@ export async function convertToolResultsToToon( ); if (typeof message.content === "string") { - try { - const unwrapped = unwrapToolContent(message.content); - const parsed = JSON.parse(unwrapped); - const noncompressed = unwrapped; - const compressed = toonEncode(parsed); - - const tokensBefore = tokenizer.countTokens([ - { role: "user", content: noncompressed }, - ]); - const tokensAfter = tokenizer.countTokens([ - { role: "user", content: compressed }, - ]); - - toolResultCount++; - - // Always count tokens - totalTokensBefore += tokensBefore; - - // Only apply compression if it actually saves tokens - if (tokensAfter < tokensBefore) { - totalTokensAfter += tokensAfter; - - logger.info( - { - toolCallId: message.tool_call_id, - beforeLength: noncompressed.length, - afterLength: compressed.length, - tokensBefore, - tokensAfter, - toonPreview: compressed.substring(0, 150), - provider, - }, - "convertToolResultsToToon: compressed", - ); - logger.trace( - { - toolCallId: message.tool_call_id, - before: noncompressed, - after: compressed, - provider, - supposedToBeJson: parsed, - }, - "convertToolResultsToToon: before/after", - ); - - return { - ...message, - content: compressed, - }; - } - - // Compression not applied - count non-compressed tokens to track total tokens anyway - totalTokensAfter += tokensBefore; - logger.info( - { - toolCallId: message.tool_call_id, - tokensBefore, - tokensAfter, - provider, - }, - "Skipping TOON compression - compressed output has more tokens", - ); - return message; - } catch { - logger.info( - { - toolCallId: message.tool_call_id, - contentPreview: - typeof message.content === "string" - ? message.content.substring(0, 100) - : "non-string", - }, - "Skipping TOON conversion - content is not JSON", - ); - return message; - } + candidates.push({ index, message, content: message.content }); } } + }); + + const encodedResults = + candidates.length > 0 + ? await toonEncodeToolResults( + candidates.map((candidate) => ({ + id: candidate.message.tool_call_id, + rawContent: candidate.content, + unwrap: true, + })), + ) + : []; + + if (encodedResults === null) { + // Native addon unavailable: fail open — keep every message uncompressed + // and surface the explicit skip reason instead of fabricating stats. + return { + messages, + stats: { + tokensBefore: 0, + tokensAfter: 0, + costSavings: 0, + wasEffective: false, + hadToolResults: candidates.length > 0, + skipReason: "addon_unavailable", + }, + }; + } + + const result = [...messages]; + candidates.forEach((candidate, candidateIndex) => { + const { normalized, encoded: compressed } = encodedResults[candidateIndex]; + const { message } = candidate; + + if (compressed === null) { + logger.info( + { + toolCallId: message.tool_call_id, + contentPreview: candidate.content.substring(0, 100), + }, + "Skipping TOON conversion - content is not JSON", + ); + return; + } + + // Token accounting on the normalized (unwrapped) string, exactly as before. + const tokensBefore = tokenizer.countTokens([ + { role: "user", content: normalized }, + ]); + const tokensAfter = tokenizer.countTokens([ + { role: "user", content: compressed }, + ]); + + toolResultCount++; + + // Always count tokens + totalTokensBefore += tokensBefore; - return message; + // Only apply compression if it actually saves tokens + if (tokensAfter < tokensBefore) { + totalTokensAfter += tokensAfter; + + logger.info( + { + toolCallId: message.tool_call_id, + beforeLength: normalized.length, + afterLength: compressed.length, + tokensBefore, + tokensAfter, + toonPreview: compressed.substring(0, 150), + provider, + }, + "convertToolResultsToToon: compressed", + ); + logger.trace( + { + toolCallId: message.tool_call_id, + before: normalized, + after: compressed, + provider, + }, + "convertToolResultsToToon: before/after", + ); + + result[candidate.index] = { + ...message, + content: compressed, + }; + return; + } + + // Compression not applied - count non-compressed tokens to track total tokens anyway + totalTokensAfter += tokensBefore; + logger.info( + { + toolCallId: message.tool_call_id, + tokensBefore, + tokensAfter, + provider, + }, + "Skipping TOON compression - compressed output has more tokens", + ); }); logger.info( diff --git a/platform/backend/src/routes/proxy/llm-proxy-handler.ts b/platform/backend/src/routes/proxy/llm-proxy-handler.ts index c77b1465bf1..4a900f32d12 100644 --- a/platform/backend/src/routes/proxy/llm-proxy-handler.ts +++ b/platform/backend/src/routes/proxy/llm-proxy-handler.ts @@ -839,7 +839,11 @@ export async function handleLLMProxy< if (shouldApplyToonCompression) { toonStats = await requestAdapter.applyToonCompression(actualModel); - if (!toonStats.hadToolResults) { + if (toonStats.skipReason) { + // Infrastructure failure (native addon unavailable) — never derive + // not_effective/no_tool_results from stats that were never computed. + toonSkipReason = toonStats.skipReason; + } else if (!toonStats.hadToolResults) { toonSkipReason = "no_tool_results"; } else if (!toonStats.wasEffective) { toonSkipReason = "not_effective"; diff --git a/platform/backend/src/routes/proxy/routes/toon-addon-unavailable.test.ts b/platform/backend/src/routes/proxy/routes/toon-addon-unavailable.test.ts new file mode 100644 index 00000000000..44ae8f77886 --- /dev/null +++ b/platform/backend/src/routes/proxy/routes/toon-addon-unavailable.test.ts @@ -0,0 +1,147 @@ +// Pins the addon-unavailable contract at the handler level: when TOON +// compression is enabled but the native proxy-transform addon cannot be +// loaded, the proxy fails open (request still succeeds, uncompressed) and the +// persisted interaction records toonSkipReason = "addon_unavailable" — never +// not_effective/no_tool_results fabricated from stats that were never computed. + +import Fastify, { type FastifyInstance } from "fastify"; +import { + serializerCompiler, + validatorCompiler, + type ZodTypeProvider, +} from "fastify-type-provider-zod"; +import { vi } from "vitest"; +import { InteractionModel } from "@/models"; +import { afterEach, beforeEach, describe, expect, test } from "@/test"; +import { openaiAdapterFactory } from "../adapters/openai"; +import * as proxyUtils from "../utils"; +import { toonEncodeToolResults } from "../utils/toon-native"; +import openAiProxyRoutes from "./openai"; + +// The native addon is unavailable: the helper fails open by resolving to null. +vi.mock("@/routes/proxy/utils/toon-native", () => ({ + toonEncodeToolResults: vi.fn(), + initToonNative: vi.fn(), +})); + +describe("LLM proxy with the TOON addon unavailable", () => { + let app: FastifyInstance; + + beforeEach(() => { + vi.mocked(toonEncodeToolResults).mockResolvedValue(null); + }); + + afterEach(async () => { + vi.restoreAllMocks(); + if (app) { + await app.close(); + } + }); + + test("persists toonSkipReason = addon_unavailable when compression is enabled", async ({ + makeAgent, + }) => { + const agent = await makeAgent({ name: "TOON addon unavailable" }); + + vi.spyOn( + proxyUtils.toonConversion, + "shouldApplyToonCompression", + ).mockResolvedValue(true); + + const upstreamRequests: unknown[] = []; + vi.spyOn(openaiAdapterFactory, "createClient").mockImplementation( + () => + ({ + chat: { + completions: { + create: async (request: unknown) => { + upstreamRequests.push(request); + return { + id: "chatcmpl_nonstream", + object: "chat.completion", + created: 1, + model: "gpt-4o", + choices: [ + { + index: 0, + message: { + role: "assistant", + content: "Mocked response", + refusal: null, + }, + finish_reason: "stop", + logprobs: null, + }, + ], + usage: { + prompt_tokens: 100, + completion_tokens: 20, + total_tokens: 120, + }, + }; + }, + }, + }, + }) as never, + ); + + app = Fastify().withTypeProvider(); + app.setValidatorCompiler(validatorCompiler); + app.setSerializerCompiler(serializerCompiler); + await app.register(openAiProxyRoutes); + + const toolResultContent = JSON.stringify({ + files: [{ name: "README.md" }, { name: "src" }], + }); + const response = await app.inject({ + method: "POST", + url: `/v1/openai/${agent.id}/chat/completions`, + headers: { + Authorization: "Bearer test-key", + "Content-Type": "application/json", + }, + payload: { + model: "gpt-4o", + messages: [ + { role: "user", content: "What files are in the current directory?" }, + { + role: "assistant", + content: null, + tool_calls: [ + { + id: "call_123", + type: "function", + function: { + name: "list_files", + arguments: '{"directory": "."}', + }, + }, + ], + }, + { + role: "tool", + tool_call_id: "call_123", + content: toolResultContent, + }, + ], + }, + }); + + // Fail-open: the request still succeeds and the tool result reaches the + // provider uncompressed. + expect(response.statusCode).toBe(200); + const lastUpstreamRequest = upstreamRequests.at(-1) as { + messages: { role: string; content?: unknown }[]; + }; + const upstreamToolMessage = lastUpstreamRequest.messages.find( + (message) => message.role === "tool", + ); + expect(upstreamToolMessage?.content).toBe(toolResultContent); + + const interactions = await InteractionModel.getAllInteractionsForProfile( + agent.id, + ); + expect(interactions).toHaveLength(1); + expect(interactions[0].toonSkipReason).toBe("addon_unavailable"); + }); +}); diff --git a/platform/backend/src/routes/proxy/utils/toon-native.golden.test.ts b/platform/backend/src/routes/proxy/utils/toon-native.golden.test.ts new file mode 100644 index 00000000000..8953ce9dce1 --- /dev/null +++ b/platform/backend/src/routes/proxy/utils/toon-native.golden.test.ts @@ -0,0 +1,74 @@ +// Runs the committed TOON v3 golden corpus (generated by the Rust +// proxy-transform-core crate) through the real native addon via the +// toon-native helper, pinning exact normalized/encoded output at the JS +// boundary. Requires the built addon: mandatory in CI (the workspace builds +// native deps before vitest); locally it skips visibly when the addon is not +// built — run `pnpm test:native` from platform/backend. + +import { readFileSync } from "node:fs"; +import path from "node:path"; +import { describe, expect, test } from "vitest"; +import { toonEncodeToolResults } from "./toon-native"; + +type GoldenEntry = { + name: string; + rawContent: string; + unwrap: boolean; + expected: { normalized: string; encoded: string | null }; +}; + +const CORPUS_PATH = path.resolve( + import.meta.dirname, + "../../../../../archestra-rs/proxy-transform-core/tests/fixtures/golden-corpus.json", +); + +const addonLoadError: unknown = await import( + "@archestra/proxy-transform-rs" +).then( + () => null, + (error) => error, +); + +// In CI the suite must FAIL (never skip) when the addon is missing. +const describeNative = + addonLoadError === null || process.env.CI ? describe : describe.skip; +if (addonLoadError !== null && !process.env.CI) { + console.warn( + `[toon-native.golden.test] skipping: @archestra/proxy-transform-rs is not built (${String( + addonLoadError, + )}). Run \`pnpm test:native\` from platform/backend.`, + ); +} + +describeNative("TOON v3 golden corpus through the native helper", () => { + test("the native addon loads", () => { + expect(addonLoadError).toBeNull(); + }); + + test("every corpus entry matches its committed golden exactly", async () => { + const corpus: GoldenEntry[] = JSON.parse(readFileSync(CORPUS_PATH, "utf8")); + expect(corpus.length).toBeGreaterThan(0); + + const results = await toonEncodeToolResults( + corpus.map((entry) => ({ + id: entry.name, + rawContent: entry.rawContent, + unwrap: entry.unwrap, + })), + ); + + expect(results).not.toBeNull(); + // Keyed by entry name so a mismatch reports which fixture diverged. + const actualByName = corpus.map((entry, index) => ({ + name: entry.name, + normalized: results?.[index].normalized, + encoded: results?.[index].encoded, + })); + const expectedByName = corpus.map((entry) => ({ + name: entry.name, + normalized: entry.expected.normalized, + encoded: entry.expected.encoded, + })); + expect(actualByName).toStrictEqual(expectedByName); + }); +}); diff --git a/platform/backend/src/routes/proxy/utils/toon-native.test.ts b/platform/backend/src/routes/proxy/utils/toon-native.test.ts new file mode 100644 index 00000000000..e890aede527 --- /dev/null +++ b/platform/backend/src/routes/proxy/utils/toon-native.test.ts @@ -0,0 +1,120 @@ +import { toonEncodeToolResults as nativeToonEncodeToolResults } from "@archestra/proxy-transform-rs"; +import { beforeEach, describe, expect, test, vi } from "vitest"; +import { metrics } from "@/observability"; +import { convertToolResultsToToon } from "../adapters/openai"; +import { toonEncodeToolResults } from "./toon-native"; + +// The native transform is a compiled addon that may be absent in the unit-test +// env; mock it at the boundary to pin the helper's wiring only. Real encoding +// is covered by toon-native.golden.test.ts and the Rust crate tests. +vi.mock("@archestra/proxy-transform-rs", () => ({ + toonEncodeToolResults: vi.fn(), +})); +vi.mock("@/observability"); + +const items = [ + { id: "call_1", rawContent: '{"a":[1,2]}', unwrap: true }, + { id: "call_2", rawContent: "not json", unwrap: true }, +]; + +describe("toonEncodeToolResults helper", () => { + beforeEach(() => { + vi.mocked(nativeToonEncodeToolResults).mockReset(); + }); + + test("returns the positional native results on success", async () => { + const nativeResults = [ + { normalized: '{"a":[1,2]}', encoded: "a[2]: 1,2" }, + { normalized: "not json", encoded: null }, + ]; + vi.mocked(nativeToonEncodeToolResults).mockResolvedValue(nativeResults); + + const results = await toonEncodeToolResults(items); + + expect(results).toStrictEqual(nativeResults); + expect(nativeToonEncodeToolResults).toHaveBeenCalledWith(items); + expect(metrics.llm.reportToonAddonUnavailable).not.toHaveBeenCalled(); + }); + + test("returns null when the native batch length does not match the input", async () => { + vi.mocked(nativeToonEncodeToolResults).mockResolvedValue([ + { normalized: '{"a":[1,2]}', encoded: "a[2]: 1,2" }, + ]); + + const results = await toonEncodeToolResults(items); + + expect(results).toBeNull(); + expect(metrics.llm.reportToonAddonUnavailable).toHaveBeenCalledWith( + "request", + ); + }); + + test("returns null (fail-open) when the native call fails", async () => { + vi.mocked(nativeToonEncodeToolResults).mockRejectedValue( + new Error("addon missing"), + ); + + const results = await toonEncodeToolResults(items); + + expect(results).toBeNull(); + expect(metrics.llm.reportToonAddonUnavailable).toHaveBeenCalledWith( + "request", + ); + }); +}); + +describe("convertToolResultsToToon with the addon unavailable", () => { + beforeEach(() => { + vi.mocked(nativeToonEncodeToolResults).mockReset(); + vi.mocked(nativeToonEncodeToolResults).mockRejectedValue( + new Error("addon missing"), + ); + }); + + test("fails open: messages untouched, explicit addon_unavailable skip reason", async () => { + const messages = [ + { role: "user" as const, content: "list files" }, + { + role: "tool" as const, + tool_call_id: "call_1", + content: '{"files":[{"name":"a"},{"name":"b"}]}', + }, + ]; + + const { messages: resultMessages, stats } = await convertToolResultsToToon( + messages, + "gpt-4o", + "openai", + ); + + expect(resultMessages).toStrictEqual(messages); + expect(stats).toStrictEqual({ + tokensBefore: 0, + tokensAfter: 0, + costSavings: 0, + wasEffective: false, + hadToolResults: true, + skipReason: "addon_unavailable", + }); + }); + + test("does not call the native addon when there are no tool results", async () => { + const messages = [{ role: "user" as const, content: "hello" }]; + + const { messages: resultMessages, stats } = await convertToolResultsToToon( + messages, + "gpt-4o", + "openai", + ); + + expect(resultMessages).toStrictEqual(messages); + expect(stats).toStrictEqual({ + tokensBefore: 0, + tokensAfter: 0, + costSavings: 0, + wasEffective: false, + hadToolResults: false, + }); + expect(nativeToonEncodeToolResults).not.toHaveBeenCalled(); + }); +}); diff --git a/platform/backend/src/routes/proxy/utils/toon-native.ts b/platform/backend/src/routes/proxy/utils/toon-native.ts new file mode 100644 index 00000000000..14f47d0620d --- /dev/null +++ b/platform/backend/src/routes/proxy/utils/toon-native.ts @@ -0,0 +1,72 @@ +// Batched TOON encoding of tool results via the native Rust addon +// (@archestra/proxy-transform-rs), off the JS thread. Fail-open: any +// load/call failure resolves to null so adapters skip compression for the +// request and the handler records the explicit `addon_unavailable` skip +// reason instead of fabricating stats. + +import type { + ToonEncodeItem, + ToonEncodeResult, +} from "@archestra/proxy-transform-rs"; +import logger from "@/logging"; +import { metrics } from "@/observability"; + +export type { ToonEncodeItem, ToonEncodeResult }; + +/** + * Transform a batch of tool results (optional client-wrapper unwrap → JSON + * parse → TOON encode). Results are positional — same order and length as + * `items`; `encoded` is null for content that is not parseable JSON. + * + * Returns null when the native addon is unavailable or misbehaves (callers + * must then skip compression entirely and surface `addon_unavailable`). + */ +export async function toonEncodeToolResults( + items: ToonEncodeItem[], +): Promise { + try { + const native = await loadProxyTransformNative(); + const results = await native.toonEncodeToolResults(items); + if (results.length !== items.length) { + throw new Error( + `native toonEncodeToolResults returned ${results.length} results for ${items.length} items`, + ); + } + return results; + } catch (error) { + logger.error( + { err: error, itemCount: items.length }, + "[toon-native] native TOON encode failed — skipping tool result compression for this request", + ); + metrics.llm.reportToonAddonUnavailable("request"); + return null; + } +} + +/** + * Eager startup probe: load the addon once so a broken deployment surfaces at + * boot (error log + metric) instead of silently skipping compression per + * request. Never throws — the proxy fails open. + */ +export async function initToonNative(): Promise { + try { + await loadProxyTransformNative(); + logger.info("[toon-native] native proxy-transform addon loaded"); + } catch (error) { + logger.error( + { err: error }, + "[toon-native] failed to load @archestra/proxy-transform-rs at startup — TOON compression will be skipped (addon_unavailable)", + ); + metrics.llm.reportToonAddonUnavailable("startup"); + } +} + +// Lazy, memoized load of the native addon: codegen and paths that never +// compress tool results don't require the built `.node`. Mirrors +// utils/image-conversion.ts and the sandbox/app-runtime native loaders. +type ProxyTransformBindings = typeof import("@archestra/proxy-transform-rs"); +let nativeBindings: Promise | null = null; +function loadProxyTransformNative(): Promise { + nativeBindings ??= import("@archestra/proxy-transform-rs"); + return nativeBindings; +} diff --git a/platform/backend/src/server.ts b/platform/backend/src/server.ts index 864cc93791d..45d2eaa35d3 100644 --- a/platform/backend/src/server.ts +++ b/platform/backend/src/server.ts @@ -104,6 +104,7 @@ import websocketService from "@/websocket"; import * as routes from "./routes"; import { publicConfigRoutes } from "./routes/config"; import { createOAuthAwareCorsDelegate } from "./routes/oauth-cors"; +import { initToonNative } from "./routes/proxy/utils/toon-native"; import { CONNECTION_SETUP_SCRIPT_PREFIX, HEALTH_PATH, @@ -1127,6 +1128,10 @@ const startWebServer = async () => { const labelKeys = await initializeObservabilityMetrics(); + // Eagerly load the native proxy-transform addon so a broken deployment + // surfaces at boot (error log + metric); the proxy itself fails open. + await initToonNative(); + // Start metrics server await startMetricsServer(); @@ -1487,6 +1492,10 @@ const startWorker = async () => { includeAgentExecutionMetrics: false, }); + // Worker mode registers LLM proxy routes too (registerWorkerRoutes below), + // so eagerly load the native proxy-transform addon here as well. + await initToonNative(); + registerTaskHandlers(taskQueueService); await taskQueueService.seedPeriodicTasks(); taskQueueService.startWorker(); diff --git a/platform/backend/src/types/interaction.ts b/platform/backend/src/types/interaction.ts index e2ed83d1f8a..0039123cb3b 100644 --- a/platform/backend/src/types/interaction.ts +++ b/platform/backend/src/types/interaction.ts @@ -485,6 +485,7 @@ export const ToonSkipReasonCountsSchema = z.object({ notEnabled: z.number(), notEffective: z.number(), noToolResults: z.number(), + addonUnavailable: z.number(), }); /** diff --git a/platform/backend/src/types/tool-result-compression.ts b/platform/backend/src/types/tool-result-compression.ts index dc24e666c76..0f8d78c8bc7 100644 --- a/platform/backend/src/types/tool-result-compression.ts +++ b/platform/backend/src/types/tool-result-compression.ts @@ -4,6 +4,7 @@ export const ToonSkipReasonSchema = z.enum([ "not_enabled", "not_effective", "no_tool_results", + "addon_unavailable", ]); export type ToonSkipReason = z.infer; @@ -21,6 +22,13 @@ export interface ToolCompressionStats { wasEffective: boolean; /** Whether there were any tool results to compress */ hadToolResults: boolean; + /** + * Set when compression could not run at all (native addon unavailable — + * an infrastructure failure, not an outcome of trying). Takes precedence + * over the reasons the handler derives from the count fields, so the + * interaction is never misreported as not_effective/no_tool_results. + */ + skipReason?: Extract; } /** diff --git a/platform/frontend/src/components/savings.tsx b/platform/frontend/src/components/savings.tsx index 2944f1ec307..b6e9319c4a0 100644 --- a/platform/frontend/src/components/savings.tsx +++ b/platform/frontend/src/components/savings.tsx @@ -140,6 +140,11 @@ export function Savings({
Tool result compression: Skipped (no token savings)
) : toonSkipReason === "no_tool_results" ? (
Tool result compression: No tool results
+ ) : toonSkipReason === "addon_unavailable" ? ( +
+ Tool result compression: Not applied (compression engine + unavailable) +
) : (
Tool result compression: Not applied
)} diff --git a/platform/frontend/src/mocks/data/interactions.ts b/platform/frontend/src/mocks/data/interactions.ts index 74d0fdc8a12..d5695a36179 100644 --- a/platform/frontend/src/mocks/data/interactions.ts +++ b/platform/frontend/src/mocks/data/interactions.ts @@ -51,6 +51,7 @@ export function makeSessionSummary( notEnabled: 0, notEffective: 0, noToolResults: 0, + addonUnavailable: 0, }, firstRequestTime: "2026-01-01T00:00:00.000Z", lastRequestTime: "2026-01-01T00:00:00.000Z", diff --git a/platform/pnpm-lock.yaml b/platform/pnpm-lock.yaml index 673ee9bbbde..48c45a818ce 100644 --- a/platform/pnpm-lock.yaml +++ b/platform/pnpm-lock.yaml @@ -193,6 +193,9 @@ importers: '@archestra/image-rs': specifier: workspace:* version: link:../archestra-rs/image-rs + '@archestra/proxy-transform-rs': + specifier: workspace:* + version: link:../archestra-rs/proxy-transform-rs '@archestra/sandbox-rs': specifier: workspace:* version: link:../archestra-rs/sandbox-rs diff --git a/platform/shared/hey-api/clients/api/types.gen.ts b/platform/shared/hey-api/clients/api/types.gen.ts index bf22518fe6b..217c7783e4b 100644 --- a/platform/shared/hey-api/clients/api/types.gen.ts +++ b/platform/shared/hey-api/clients/api/types.gen.ts @@ -31811,7 +31811,7 @@ export type GetInteractionsResponses = { toonTokensBefore: number | null; toonTokensAfter: number | null; toonCostSavings: string | null; - toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results'; + toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results' | 'addon_unavailable'; createdAt: string; chatErrors?: Array<{ id: string; @@ -32005,7 +32005,7 @@ export type GetInteractionsResponses = { toonTokensBefore: number | null; toonTokensAfter: number | null; toonCostSavings: string | null; - toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results'; + toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results' | 'addon_unavailable'; createdAt: string; chatErrors?: Array<{ id: string; @@ -32114,7 +32114,7 @@ export type GetInteractionsResponses = { toonTokensBefore: number | null; toonTokensAfter: number | null; toonCostSavings: string | null; - toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results'; + toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results' | 'addon_unavailable'; createdAt: string; chatErrors?: Array<{ id: string; @@ -32221,7 +32221,7 @@ export type GetInteractionsResponses = { toonTokensBefore: number | null; toonTokensAfter: number | null; toonCostSavings: string | null; - toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results'; + toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results' | 'addon_unavailable'; createdAt: string; chatErrors?: Array<{ id: string; @@ -32306,7 +32306,7 @@ export type GetInteractionsResponses = { toonTokensBefore: number | null; toonTokensAfter: number | null; toonCostSavings: string | null; - toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results'; + toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results' | 'addon_unavailable'; createdAt: string; chatErrors?: Array<{ id: string; @@ -32393,7 +32393,7 @@ export type GetInteractionsResponses = { toonTokensBefore: number | null; toonTokensAfter: number | null; toonCostSavings: string | null; - toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results'; + toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results' | 'addon_unavailable'; createdAt: string; chatErrors?: Array<{ id: string; @@ -32904,7 +32904,7 @@ export type GetInteractionsResponses = { toonTokensBefore: number | null; toonTokensAfter: number | null; toonCostSavings: string | null; - toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results'; + toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results' | 'addon_unavailable'; createdAt: string; chatErrors?: Array<{ id: string; @@ -32991,7 +32991,7 @@ export type GetInteractionsResponses = { toonTokensBefore: number | null; toonTokensAfter: number | null; toonCostSavings: string | null; - toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results'; + toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results' | 'addon_unavailable'; createdAt: string; chatErrors?: Array<{ id: string; @@ -33078,7 +33078,7 @@ export type GetInteractionsResponses = { toonTokensBefore: number | null; toonTokensAfter: number | null; toonCostSavings: string | null; - toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results'; + toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results' | 'addon_unavailable'; createdAt: string; chatErrors?: Array<{ id: string; @@ -33165,7 +33165,7 @@ export type GetInteractionsResponses = { toonTokensBefore: number | null; toonTokensAfter: number | null; toonCostSavings: string | null; - toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results'; + toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results' | 'addon_unavailable'; createdAt: string; chatErrors?: Array<{ id: string; @@ -33252,7 +33252,7 @@ export type GetInteractionsResponses = { toonTokensBefore: number | null; toonTokensAfter: number | null; toonCostSavings: string | null; - toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results'; + toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results' | 'addon_unavailable'; createdAt: string; chatErrors?: Array<{ id: string; @@ -33339,7 +33339,7 @@ export type GetInteractionsResponses = { toonTokensBefore: number | null; toonTokensAfter: number | null; toonCostSavings: string | null; - toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results'; + toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results' | 'addon_unavailable'; createdAt: string; chatErrors?: Array<{ id: string; @@ -33426,7 +33426,7 @@ export type GetInteractionsResponses = { toonTokensBefore: number | null; toonTokensAfter: number | null; toonCostSavings: string | null; - toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results'; + toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results' | 'addon_unavailable'; createdAt: string; chatErrors?: Array<{ id: string; @@ -33513,7 +33513,7 @@ export type GetInteractionsResponses = { toonTokensBefore: number | null; toonTokensAfter: number | null; toonCostSavings: string | null; - toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results'; + toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results' | 'addon_unavailable'; createdAt: string; chatErrors?: Array<{ id: string; @@ -33598,7 +33598,7 @@ export type GetInteractionsResponses = { toonTokensBefore: number | null; toonTokensAfter: number | null; toonCostSavings: string | null; - toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results'; + toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results' | 'addon_unavailable'; createdAt: string; chatErrors?: Array<{ id: string; @@ -33683,7 +33683,7 @@ export type GetInteractionsResponses = { toonTokensBefore: number | null; toonTokensAfter: number | null; toonCostSavings: string | null; - toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results'; + toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results' | 'addon_unavailable'; createdAt: string; chatErrors?: Array<{ id: string; @@ -33770,7 +33770,7 @@ export type GetInteractionsResponses = { toonTokensBefore: number | null; toonTokensAfter: number | null; toonCostSavings: string | null; - toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results'; + toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results' | 'addon_unavailable'; createdAt: string; chatErrors?: Array<{ id: string; @@ -33857,7 +33857,7 @@ export type GetInteractionsResponses = { toonTokensBefore: number | null; toonTokensAfter: number | null; toonCostSavings: string | null; - toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results'; + toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results' | 'addon_unavailable'; createdAt: string; chatErrors?: Array<{ id: string; @@ -34585,7 +34585,7 @@ export type GetInteractionsResponses = { toonTokensBefore: number | null; toonTokensAfter: number | null; toonCostSavings: string | null; - toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results'; + toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results' | 'addon_unavailable'; createdAt: string; chatErrors?: Array<{ id: string; @@ -34672,7 +34672,7 @@ export type GetInteractionsResponses = { toonTokensBefore: number | null; toonTokensAfter: number | null; toonCostSavings: string | null; - toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results'; + toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results' | 'addon_unavailable'; createdAt: string; chatErrors?: Array<{ id: string; @@ -34826,7 +34826,7 @@ export type GetInteractionsResponses = { toonTokensBefore: number | null; toonTokensAfter: number | null; toonCostSavings: string | null; - toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results'; + toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results' | 'addon_unavailable'; createdAt: string; chatErrors?: Array<{ id: string; @@ -35020,7 +35020,7 @@ export type GetInteractionsResponses = { toonTokensBefore: number | null; toonTokensAfter: number | null; toonCostSavings: string | null; - toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results'; + toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results' | 'addon_unavailable'; createdAt: string; chatErrors?: Array<{ id: string; @@ -35192,6 +35192,7 @@ export type GetInteractionSessionsResponses = { notEnabled: number; notEffective: number; noToolResults: number; + addonUnavailable: number; }; firstRequestTime: string; lastRequestTime: string; @@ -35524,7 +35525,7 @@ export type GetInteractionResponses = { toonTokensBefore: number | null; toonTokensAfter: number | null; toonCostSavings: string | null; - toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results'; + toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results' | 'addon_unavailable'; createdAt: string; chatErrors?: Array<{ id: string; @@ -35718,7 +35719,7 @@ export type GetInteractionResponses = { toonTokensBefore: number | null; toonTokensAfter: number | null; toonCostSavings: string | null; - toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results'; + toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results' | 'addon_unavailable'; createdAt: string; chatErrors?: Array<{ id: string; @@ -35827,7 +35828,7 @@ export type GetInteractionResponses = { toonTokensBefore: number | null; toonTokensAfter: number | null; toonCostSavings: string | null; - toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results'; + toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results' | 'addon_unavailable'; createdAt: string; chatErrors?: Array<{ id: string; @@ -35934,7 +35935,7 @@ export type GetInteractionResponses = { toonTokensBefore: number | null; toonTokensAfter: number | null; toonCostSavings: string | null; - toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results'; + toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results' | 'addon_unavailable'; createdAt: string; chatErrors?: Array<{ id: string; @@ -36019,7 +36020,7 @@ export type GetInteractionResponses = { toonTokensBefore: number | null; toonTokensAfter: number | null; toonCostSavings: string | null; - toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results'; + toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results' | 'addon_unavailable'; createdAt: string; chatErrors?: Array<{ id: string; @@ -36106,7 +36107,7 @@ export type GetInteractionResponses = { toonTokensBefore: number | null; toonTokensAfter: number | null; toonCostSavings: string | null; - toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results'; + toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results' | 'addon_unavailable'; createdAt: string; chatErrors?: Array<{ id: string; @@ -36617,7 +36618,7 @@ export type GetInteractionResponses = { toonTokensBefore: number | null; toonTokensAfter: number | null; toonCostSavings: string | null; - toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results'; + toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results' | 'addon_unavailable'; createdAt: string; chatErrors?: Array<{ id: string; @@ -36704,7 +36705,7 @@ export type GetInteractionResponses = { toonTokensBefore: number | null; toonTokensAfter: number | null; toonCostSavings: string | null; - toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results'; + toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results' | 'addon_unavailable'; createdAt: string; chatErrors?: Array<{ id: string; @@ -36791,7 +36792,7 @@ export type GetInteractionResponses = { toonTokensBefore: number | null; toonTokensAfter: number | null; toonCostSavings: string | null; - toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results'; + toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results' | 'addon_unavailable'; createdAt: string; chatErrors?: Array<{ id: string; @@ -36878,7 +36879,7 @@ export type GetInteractionResponses = { toonTokensBefore: number | null; toonTokensAfter: number | null; toonCostSavings: string | null; - toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results'; + toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results' | 'addon_unavailable'; createdAt: string; chatErrors?: Array<{ id: string; @@ -36965,7 +36966,7 @@ export type GetInteractionResponses = { toonTokensBefore: number | null; toonTokensAfter: number | null; toonCostSavings: string | null; - toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results'; + toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results' | 'addon_unavailable'; createdAt: string; chatErrors?: Array<{ id: string; @@ -37052,7 +37053,7 @@ export type GetInteractionResponses = { toonTokensBefore: number | null; toonTokensAfter: number | null; toonCostSavings: string | null; - toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results'; + toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results' | 'addon_unavailable'; createdAt: string; chatErrors?: Array<{ id: string; @@ -37139,7 +37140,7 @@ export type GetInteractionResponses = { toonTokensBefore: number | null; toonTokensAfter: number | null; toonCostSavings: string | null; - toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results'; + toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results' | 'addon_unavailable'; createdAt: string; chatErrors?: Array<{ id: string; @@ -37226,7 +37227,7 @@ export type GetInteractionResponses = { toonTokensBefore: number | null; toonTokensAfter: number | null; toonCostSavings: string | null; - toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results'; + toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results' | 'addon_unavailable'; createdAt: string; chatErrors?: Array<{ id: string; @@ -37311,7 +37312,7 @@ export type GetInteractionResponses = { toonTokensBefore: number | null; toonTokensAfter: number | null; toonCostSavings: string | null; - toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results'; + toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results' | 'addon_unavailable'; createdAt: string; chatErrors?: Array<{ id: string; @@ -37396,7 +37397,7 @@ export type GetInteractionResponses = { toonTokensBefore: number | null; toonTokensAfter: number | null; toonCostSavings: string | null; - toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results'; + toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results' | 'addon_unavailable'; createdAt: string; chatErrors?: Array<{ id: string; @@ -37483,7 +37484,7 @@ export type GetInteractionResponses = { toonTokensBefore: number | null; toonTokensAfter: number | null; toonCostSavings: string | null; - toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results'; + toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results' | 'addon_unavailable'; createdAt: string; chatErrors?: Array<{ id: string; @@ -37570,7 +37571,7 @@ export type GetInteractionResponses = { toonTokensBefore: number | null; toonTokensAfter: number | null; toonCostSavings: string | null; - toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results'; + toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results' | 'addon_unavailable'; createdAt: string; chatErrors?: Array<{ id: string; @@ -38298,7 +38299,7 @@ export type GetInteractionResponses = { toonTokensBefore: number | null; toonTokensAfter: number | null; toonCostSavings: string | null; - toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results'; + toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results' | 'addon_unavailable'; createdAt: string; chatErrors?: Array<{ id: string; @@ -38385,7 +38386,7 @@ export type GetInteractionResponses = { toonTokensBefore: number | null; toonTokensAfter: number | null; toonCostSavings: string | null; - toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results'; + toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results' | 'addon_unavailable'; createdAt: string; chatErrors?: Array<{ id: string; @@ -38539,7 +38540,7 @@ export type GetInteractionResponses = { toonTokensBefore: number | null; toonTokensAfter: number | null; toonCostSavings: string | null; - toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results'; + toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results' | 'addon_unavailable'; createdAt: string; chatErrors?: Array<{ id: string; @@ -38733,7 +38734,7 @@ export type GetInteractionResponses = { toonTokensBefore: number | null; toonTokensAfter: number | null; toonCostSavings: string | null; - toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results'; + toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results' | 'addon_unavailable'; createdAt: string; chatErrors?: Array<{ id: string; From 801687727a0e9de0d636a5d542bd147134b21b2c Mon Sep 17 00:00:00 2001 From: Arseny Kravchenko Date: Fri, 10 Jul 2026 11:46:37 +0200 Subject: [PATCH 05/18] build(platform): wire @archestra/proxy-transform-rs into build, dev, and Docker turbo build task (cache:false) + @backend#check:ci dependsOn; biome exclusion for the generated index.d.ts; unconditional Tilt prebuild resource; dev-stack.sh addon list; Dockerfile manifest/node_modules/ source copies and musl smoke filter. pnpm deploy output verified to ship index.cjs + index.d.ts + the .node binary with napi-loader resolvable; musl smoke stage itself left to CI. Claude-Session: https://claude.ai/code/session_01QkeLJprfeq75d2NY39aFiu --- platform/Dockerfile | 10 +++++++--- platform/biome.json | 3 ++- platform/dev/Tiltfile.dev | 20 ++++++++++++++++++++ platform/scripts/dev-stack.sh | 17 +++++++++-------- platform/turbo.json | 6 +++++- 5 files changed, 43 insertions(+), 13 deletions(-) diff --git a/platform/Dockerfile b/platform/Dockerfile index 0f4ac9ac8d6..938221972bf 100644 --- a/platform/Dockerfile +++ b/platform/Dockerfile @@ -154,6 +154,7 @@ COPY archestra-rs/napi-loader/package.json archestra-rs/napi-loader/ COPY archestra-rs/sandbox-rs/package.json archestra-rs/sandbox-rs/ COPY archestra-rs/app-runtime-rs/package.json archestra-rs/app-runtime-rs/ COPY archestra-rs/image-rs/package.json archestra-rs/image-rs/ +COPY archestra-rs/proxy-transform-rs/package.json archestra-rs/proxy-transform-rs/ # Install all dependencies RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile @@ -195,6 +196,7 @@ COPY --from=deps /app/shared/node_modules ./shared/node_modules COPY --from=deps /app/archestra-rs/sandbox-rs/node_modules ./archestra-rs/sandbox-rs/node_modules COPY --from=deps /app/archestra-rs/app-runtime-rs/node_modules ./archestra-rs/app-runtime-rs/node_modules COPY --from=deps /app/archestra-rs/image-rs/node_modules ./archestra-rs/image-rs/node_modules +COPY --from=deps /app/archestra-rs/proxy-transform-rs/node_modules ./archestra-rs/proxy-transform-rs/node_modules # Copy source files COPY package.json pnpm-lock.yaml pnpm-workspace.yaml turbo.json ./ @@ -209,6 +211,8 @@ COPY archestra-rs/app-runtime-core ./archestra-rs/app-runtime-core COPY archestra-rs/app-runtime-rs ./archestra-rs/app-runtime-rs COPY archestra-rs/image-core ./archestra-rs/image-core COPY archestra-rs/image-rs ./archestra-rs/image-rs +COPY archestra-rs/proxy-transform-core ./archestra-rs/proxy-transform-core +COPY archestra-rs/proxy-transform-rs ./archestra-rs/proxy-transform-rs # Build all workspace ENV NEXT_TELEMETRY_DISABLED=1 @@ -224,7 +228,7 @@ FROM builder-base AS rust-napi-musl-smoke RUN --mount=type=cache,target=/root/.cargo/registry \ --mount=type=cache,target=/root/.cargo/git \ --mount=type=cache,target=/app/archestra-rs/target \ - pnpm --filter @archestra/sandbox-rs --filter @archestra/app-runtime-rs --filter @archestra/image-rs check:musl + pnpm --filter @archestra/sandbox-rs --filter @archestra/app-runtime-rs --filter @archestra/image-rs --filter @archestra/proxy-transform-rs check:musl # <----- Builder stage -----> FROM builder-base AS builder @@ -259,7 +263,7 @@ RUN --mount=type=cache,target=/root/.cargo/registry \ # Assemble the production backend with `pnpm deploy` — pnpm's recommended # monorepo packaging step (https://pnpm.io/cli/deploy). It produces a # self-contained /prod/backend whose node_modules includes every workspace -# dependency (the three NAPI crates, @archestra/napi-loader, @archestra/shared) +# dependency (the four NAPI crates, @archestra/napi-loader, @archestra/shared) # alongside their transitive production deps, with each crate able to resolve # @archestra/napi-loader from a nested store. This replaces the old hand-rolled # COPY of each crate's *.node / index.cjs into a workspace-shaped, root-hoisted @@ -435,7 +439,7 @@ RUN rm -rf /root/.cache/node/corepack /usr/local/bin/pnpm /usr/local/bin/pnpx # Copy the fully-assembled backend from the builder's `pnpm deploy` output. # /prod/backend contains backend/dist, scripts, drizzle.config.ts and the # migrations (scoped by the backend package's `files` field) plus a -# self-contained node_modules — including the three NAPI crates with their +# self-contained node_modules — including the four NAPI crates with their # compiled *.node addons and a resolvable @archestra/napi-loader. This single # tree replaces the previous per-crate .node/index.cjs copy choreography and # the runtime `pnpm install`. diff --git a/platform/biome.json b/platform/biome.json index 87a923cdf1b..7412ab1a348 100644 --- a/platform/biome.json +++ b/platform/biome.json @@ -20,7 +20,8 @@ "!helm/**/*", "!**/*.gen.ts", "!**/sandbox-rs/index.d.ts", - "!**/app-runtime-rs/index.d.ts" + "!**/app-runtime-rs/index.d.ts", + "!**/proxy-transform-rs/index.d.ts" ] }, "formatter": { diff --git a/platform/dev/Tiltfile.dev b/platform/dev/Tiltfile.dev index 3446de405bb..d2c39127ec3 100644 --- a/platform/dev/Tiltfile.dev +++ b/platform/dev/Tiltfile.dev @@ -73,6 +73,8 @@ if code_runtime_enabled: backend_resource_deps.append('sandbox-rs-build') # The MCP App runtime addon must be built before the backend boots. backend_resource_deps.append('app-runtime-rs-build') +# The LLM proxy's TOON transform addon must be built before the backend boots. +backend_resource_deps.append('proxy-transform-rs-build') local_resource( 'pnpm-install', @@ -106,6 +108,24 @@ local_resource( labels=['apps'], ) +# Rebuild the native proxy-transform addon (napi) when its Rust sources change. +# The LLM proxy's TOON compression loads it, so it must exist before the +# backend boots. Mirrors app-runtime-rs-build. +local_resource( + 'proxy-transform-rs-build', + cmd='pnpm --filter @archestra/proxy-transform-rs build:dev', + deps=[ + '../archestra-rs/proxy-transform-rs/src', + '../archestra-rs/proxy-transform-core/src', + '../archestra-rs/proxy-transform-rs/Cargo.toml', + '../archestra-rs/proxy-transform-core/Cargo.toml', + '../archestra-rs/Cargo.toml', + '../archestra-rs/Cargo.lock', + ], + resource_deps=['pnpm-install'], + labels=['dev'], +) + # Main dev/prod bundle if is_prod: local_resource( diff --git a/platform/scripts/dev-stack.sh b/platform/scripts/dev-stack.sh index ad77fb2b2d5..30d45382e99 100755 --- a/platform/scripts/dev-stack.sh +++ b/platform/scripts/dev-stack.sh @@ -58,24 +58,25 @@ require_platform_cwd() { fi } -# The backend lazily loads three NAPI addons (@archestra/app-runtime-rs, -# sandbox-rs, image-rs) whose compiled `.node` binaries are gitignored and are -# never built by `pnpm install` (install scripts are disabled repo-wide), so a -# fresh worktree fails with "Unable to load @archestra/ for " -# the first time an app/sandbox/image feature is touched. Build the missing -# ones before launching Tilt. Skip crates whose binary already exists — a +# The backend lazily loads four NAPI addons (@archestra/app-runtime-rs, +# sandbox-rs, image-rs, proxy-transform-rs) whose compiled `.node` binaries are +# gitignored and are never built by `pnpm install` (install scripts are +# disabled repo-wide), so a fresh worktree fails with "Unable to load +# @archestra/ for " the first time an +# app/sandbox/image/LLM-proxy feature is touched. Build the missing ones +# before launching Tilt. Skip crates whose binary already exists — a # stale-but-present binary is rebuilt manually via `pnpm --filter build` # — so restarts stay fast. ensure_native_addons() { local platform_dir="$1" crate filters=() - for crate in app-runtime-rs sandbox-rs image-rs; do + for crate in app-runtime-rs sandbox-rs image-rs proxy-transform-rs; do if ! ls "$platform_dir/archestra-rs/$crate"/*.node >/dev/null 2>&1; then filters+=("--filter" "@archestra/$crate") fi done [ ${#filters[@]} -eq 0 ] && return 0 if ! command -v cargo >/dev/null 2>&1; then - echo "⚠ Rust toolchain not found; skipping native addon build. App/sandbox/image features will fail until you install Rust and run: pnpm --filter '@archestra/*-rs' build" >&2 + echo "⚠ Rust toolchain not found; skipping native addon build. App/sandbox/image/LLM-proxy features will fail until you install Rust and run: pnpm --filter '@archestra/*-rs' build" >&2 return 0 fi echo "→ Building missing native addons (first build takes a few minutes): ${filters[*]}" >&2 diff --git a/platform/turbo.json b/platform/turbo.json index 5ef58feaaeb..17d69db9177 100644 --- a/platform/turbo.json +++ b/platform/turbo.json @@ -20,6 +20,10 @@ "dependsOn": ["^build"], "cache": false }, + "@archestra/proxy-transform-rs#build": { + "dependsOn": ["^build"], + "cache": false + }, "dev": { "persistent": true, "cache": false, @@ -42,7 +46,7 @@ "inputs": ["$TURBO_DEFAULT$", "**/*.tsx", "**/*.ts", "./biome.json", "**/knip.config.ts", "**/package.json", "src/database/migrations/**"] }, "@backend#check:ci": { - "dependsOn": ["@archestra/app-runtime-rs#build", "@archestra/sandbox-rs#build"] + "dependsOn": ["@archestra/app-runtime-rs#build", "@archestra/sandbox-rs#build", "@archestra/proxy-transform-rs#build"] }, "check:commit": { "inputs": ["$TURBO_DEFAULT$", "**/*.tsx", "**/*.ts", "./biome.json", "**/knip.config.ts", "**/package.json", "src/database/migrations/**"] From 5690f1ed0ce40b921c27dbd558b210997d5c6f2e Mon Sep 17 00:00:00 2001 From: Arseny Kravchenko Date: Fri, 10 Jul 2026 12:29:12 +0200 Subject: [PATCH 06/18] feat(proxy): cut remaining six adapters over to the native TOON kernel anthropic, gemini, bedrock, zhipuai, minimax, cohere now mirror the OpenAI reference: one batched native call per request, positional application via structural locators, fail-open with the addon_unavailable skip reason. Each adapter's accounting semantics are preserved exactly and pinned by per-adapter stats matrices: anthropic per-block counting on shared tool_use_ids, gemini tokenizing the original serialization while parsing unwrapped, bedrock unconditional apply on both branches with content[0] semantics, zhipuai rejected-in-both-totals, minimax unconditional, cohere wins-only. Non-string stringify results are skipped per item so they can never poison the batch. Claude-Session: https://claude.ai/code/session_01QkeLJprfeq75d2NY39aFiu --- platform/backend/package.json | 2 +- .../anthropic-toon-compression.test.ts | 405 +++++++++++++ .../src/routes/proxy/adapters/anthropic.ts | 400 +++++++------ .../adapters/bedrock-toon-compression.test.ts | 539 ++++++++++++++++++ .../src/routes/proxy/adapters/bedrock.ts | 205 ++++--- .../adapters/cohere-toon-compression.test.ts | 292 ++++++++++ .../src/routes/proxy/adapters/cohere.ts | 168 +++--- .../adapters/gemini-toon-compression.test.ts | 336 +++++++++++ .../src/routes/proxy/adapters/gemini.ts | 242 +++++--- .../adapters/minimax-toon-compression.test.ts | 300 ++++++++++ .../src/routes/proxy/adapters/minimax.ts | 125 ++-- ...toon-compression-addon-unavailable.test.ts | 226 ++++++++ .../adapters/zhipuai-toon-compression.test.ts | 298 ++++++++++ .../src/routes/proxy/adapters/zhipuai.ts | 190 +++--- 14 files changed, 3208 insertions(+), 520 deletions(-) create mode 100644 platform/backend/src/routes/proxy/adapters/anthropic-toon-compression.test.ts create mode 100644 platform/backend/src/routes/proxy/adapters/bedrock-toon-compression.test.ts create mode 100644 platform/backend/src/routes/proxy/adapters/cohere-toon-compression.test.ts create mode 100644 platform/backend/src/routes/proxy/adapters/gemini-toon-compression.test.ts create mode 100644 platform/backend/src/routes/proxy/adapters/minimax-toon-compression.test.ts create mode 100644 platform/backend/src/routes/proxy/adapters/toon-compression-addon-unavailable.test.ts create mode 100644 platform/backend/src/routes/proxy/adapters/zhipuai-toon-compression.test.ts diff --git a/platform/backend/package.json b/platform/backend/package.json index f641cdd172d..87d0811653e 100644 --- a/platform/backend/package.json +++ b/platform/backend/package.json @@ -19,7 +19,7 @@ "dev": "tsdown --watch", "dev:debug": "DEBUG=1 tsdown --watch", "test": "vitest", - "test:native": "pnpm --filter @archestra/proxy-transform-rs build && vitest run src/routes/proxy/utils/toon-native.golden.test.ts src/routes/proxy/adapters/openai-toon-compression.test.ts src/routes/proxy/routes/provider-matrix.test.ts", + "test:native": "pnpm --filter @archestra/proxy-transform-rs build && vitest run src/routes/proxy/utils/toon-native.golden.test.ts src/routes/proxy/adapters/*-toon-compression.test.ts src/routes/proxy/routes/provider-matrix.test.ts", "lint": "biome check", "lint:fix": "biome check --write", "lint:fix:unsafe": "biome check --write --unsafe", diff --git a/platform/backend/src/routes/proxy/adapters/anthropic-toon-compression.test.ts b/platform/backend/src/routes/proxy/adapters/anthropic-toon-compression.test.ts new file mode 100644 index 00000000000..551179237a6 --- /dev/null +++ b/platform/backend/src/routes/proxy/adapters/anthropic-toon-compression.test.ts @@ -0,0 +1,405 @@ +// Pins the Anthropic adapter's TOON compression cutover to the native addon: +// full transformed-request exact equality (TOON content from the committed v3 +// golden corpus, everything else byte-equal) and the exact +// ToolCompressionStats accounting matrix. Anthropic-specific semantics pinned +// here: candidates come from BOTH tool_result content shapes (string content +// and every text sub-block of array content — several blocks can share one +// tool_use_id), each text block is counted individually, rejected payloads +// count their original tokens in both totals, and hadToolResults reflects +// every non-error tool_result block (even unparseable ones). Requires the +// built addon: mandatory in CI; locally it skips visibly — run +// `pnpm test:native` from platform/backend. + +import { readFileSync } from "node:fs"; +import path from "node:path"; +import { ModelModel } from "@/models"; +import { describe, expect, test } from "@/test"; +import { getTokenizer } from "@/tokenizers"; +import type { Anthropic } from "@/types"; +import { anthropicAdapterFactory } from "./anthropic"; + +type AnthropicRequest = Anthropic.Types.MessagesRequest; + +type GoldenEntry = { + name: string; + rawContent: string; + unwrap: boolean; + expected: { normalized: string; encoded: string | null }; +}; + +const CORPUS_PATH = path.resolve( + import.meta.dirname, + "../../../../../archestra-rs/proxy-transform-core/tests/fixtures/golden-corpus.json", +); +const corpus: GoldenEntry[] = JSON.parse(readFileSync(CORPUS_PATH, "utf8")); +function corpusEntry(name: string): GoldenEntry { + const entry = corpus.find((candidate) => candidate.name === name); + if (!entry) throw new Error(`golden corpus entry not found: ${name}`); + return entry; +} + +// Corpus picks: a uniform array (compression wins), a wrapped +// [{type:"text",...}] payload (unwrapped, compression wins), malformed JSON +// (kept as-is), and a near-boundary object whose TOON encoding does not save +// tokens under the Anthropic tokenizer (rejected). +const UNIFORM = corpusEntry("provider-matrix-tool-result"); +const WRAPPED = corpusEntry("wrapped-single-text"); +const MALFORMED = corpusEntry("malformed-prose"); +const NEAR_BOUNDARY = corpusEntry("boundary-obj-1"); + +const tokenizer = getTokenizer("anthropic"); +const countTokens = (content: string) => + tokenizer.countTokens([{ role: "user", content }]); + +// $1,000,000 per million input tokens = $1 per token, so expected costSavings +// equals tokens saved exactly. +async function upsertOneDollarPerTokenPricing() { + await ModelModel.upsert({ + externalId: "anthropic/claude-sonnet-4-5", + provider: "anthropic", + modelId: "claude-sonnet-4-5", + inputModalities: null, + outputModalities: null, + customPricePerMillionInput: "1000000.00", + customPricePerMillionOutput: "1000000.00", + lastSyncedAt: new Date(), + }); +} + +const addonLoadError: unknown = await import( + "@archestra/proxy-transform-rs" +).then( + () => null, + (error) => error, +); + +// In CI the suite must FAIL (never skip) when the addon is missing. +const describeNative = + addonLoadError === null || process.env.CI ? describe : describe.skip; +if (addonLoadError !== null && !process.env.CI) { + console.warn( + `[anthropic-toon-compression.test] skipping: @archestra/proxy-transform-rs is not built (${String( + addonLoadError, + )}). Run \`pnpm test:native\` from platform/backend.`, + ); +} + +describeNative("Anthropic adapter TOON compression (native addon)", () => { + test("transforms the full provider request exactly (goldens for TOON, byte-equal elsewhere)", async () => { + await upsertOneDollarPerTokenPricing(); + + const makeRequest = (): AnthropicRequest => ({ + model: "claude-sonnet-4-5", + max_tokens: 1024, + temperature: 0.25, + messages: [ + { role: "user", content: "What files are in the current directory?" }, + { + role: "assistant", + content: [ + { + type: "tool_use", + id: "toolu_list", + name: "list_files", + input: { directory: "." }, + }, + ], + }, + { + role: "user", + content: [ + { + type: "tool_result", + tool_use_id: "toolu_uniform", + content: UNIFORM.rawContent, + }, + { + type: "tool_result", + tool_use_id: "toolu_wrapped", + content: WRAPPED.rawContent, + }, + { + type: "tool_result", + tool_use_id: "toolu_malformed", + content: MALFORMED.rawContent, + }, + { + type: "tool_result", + tool_use_id: "toolu_boundary", + content: NEAR_BOUNDARY.rawContent, + }, + ], + }, + ], + }); + + const adapter = anthropicAdapterFactory.createRequestAdapter(makeRequest()); + const stats = await adapter.applyToonCompression("claude-sonnet-4-5"); + + const expectedRequest = makeRequest(); + // Compression wins for the uniform array and the wrapped payload; the + // malformed and near-boundary results keep their original content. + expectedRequest.messages[2].content = [ + { + type: "tool_result", + tool_use_id: "toolu_uniform", + content: UNIFORM.expected.encoded as string, + }, + { + type: "tool_result", + tool_use_id: "toolu_wrapped", + content: WRAPPED.expected.encoded as string, + }, + { + type: "tool_result", + tool_use_id: "toolu_malformed", + content: MALFORMED.rawContent, + }, + { + type: "tool_result", + tool_use_id: "toolu_boundary", + content: NEAR_BOUNDARY.rawContent, + }, + ]; + expect(adapter.toProviderRequest()).toStrictEqual(expectedRequest); + + // Rejected payloads count their original tokens in BOTH totals; malformed + // content is not counted at all. + const boundaryTokens = countTokens(NEAR_BOUNDARY.expected.normalized); + const tokensBefore = + countTokens(UNIFORM.expected.normalized) + + countTokens(WRAPPED.expected.normalized) + + boundaryTokens; + const tokensAfter = + countTokens(UNIFORM.expected.encoded as string) + + countTokens(WRAPPED.expected.encoded as string) + + boundaryTokens; + expect(stats).toStrictEqual({ + tokensBefore, + tokensAfter, + costSavings: tokensBefore - tokensAfter, + wasEffective: true, + hadToolResults: true, + }); + }); + + test("compresses every text block of a multi-block tool_result sharing one tool_use_id", async () => { + await upsertOneDollarPerTokenPricing(); + + // One tool_result carries three text blocks under a single tool_use_id: + // positional (locator-based) application must compress the first and + // second independently while keeping the malformed third — matching by + // id would misapply results. + const makeRequest = (): AnthropicRequest => ({ + model: "claude-sonnet-4-5", + max_tokens: 1024, + messages: [ + { role: "user", content: "Fetch the data." }, + { + role: "user", + content: [ + { + type: "tool_result", + tool_use_id: "toolu_multi", + content: [ + { type: "text", text: UNIFORM.rawContent }, + { type: "text", text: WRAPPED.rawContent }, + { type: "text", text: MALFORMED.rawContent }, + ], + }, + ], + }, + ], + }); + + const adapter = anthropicAdapterFactory.createRequestAdapter(makeRequest()); + const stats = await adapter.applyToonCompression("claude-sonnet-4-5"); + + const expectedRequest = makeRequest(); + expectedRequest.messages[1].content = [ + { + type: "tool_result", + tool_use_id: "toolu_multi", + content: [ + { type: "text", text: UNIFORM.expected.encoded as string }, + { type: "text", text: WRAPPED.expected.encoded as string }, + { type: "text", text: MALFORMED.rawContent }, + ], + }, + ]; + expect(adapter.toProviderRequest()).toStrictEqual(expectedRequest); + + // Each text block is counted individually. + const tokensBefore = + countTokens(UNIFORM.expected.normalized) + + countTokens(WRAPPED.expected.normalized); + const tokensAfter = + countTokens(UNIFORM.expected.encoded as string) + + countTokens(WRAPPED.expected.encoded as string); + expect(stats).toStrictEqual({ + tokensBefore, + tokensAfter, + costSavings: tokensBefore - tokensAfter, + wasEffective: true, + hadToolResults: true, + }); + }); + + test("applies native results to the right blocks when non-candidate blocks are interleaved", async () => { + await upsertOneDollarPerTokenPricing(); + + // Non-candidates between candidates: a plain text block, an is_error + // tool_result (whose content WOULD compress if wrongly collected), and a + // malformed text block inside an array tool_result. The native result + // index diverges from every structural index — off-by-one positional + // application would compress the wrong block and fail the equality below. + const makeRequest = (): AnthropicRequest => ({ + model: "claude-sonnet-4-5", + max_tokens: 1024, + messages: [ + { role: "user", content: "Inspect the workspace." }, + { + role: "user", + content: [ + { type: "text", text: "status update" }, + { + type: "tool_result", + tool_use_id: "toolu_error", + is_error: true, + content: UNIFORM.rawContent, + }, + { + type: "tool_result", + tool_use_id: "toolu_boundary", + content: NEAR_BOUNDARY.rawContent, + }, + ], + }, + { role: "assistant", content: "Checking the results." }, + { + role: "user", + content: [ + { + type: "tool_result", + tool_use_id: "toolu_mixed", + content: [ + { type: "text", text: MALFORMED.rawContent }, + { type: "text", text: UNIFORM.rawContent }, + ], + }, + ], + }, + ], + }); + + const adapter = anthropicAdapterFactory.createRequestAdapter(makeRequest()); + const stats = await adapter.applyToonCompression("claude-sonnet-4-5"); + + // Only the uniform text block compresses; the error result, the + // near-boundary result, and the malformed block are untouched. + const expectedRequest = makeRequest(); + expectedRequest.messages[3].content = [ + { + type: "tool_result", + tool_use_id: "toolu_mixed", + content: [ + { type: "text", text: MALFORMED.rawContent }, + { type: "text", text: UNIFORM.expected.encoded as string }, + ], + }, + ]; + expect(adapter.toProviderRequest()).toStrictEqual(expectedRequest); + + const boundaryTokens = countTokens(NEAR_BOUNDARY.expected.normalized); + const tokensBefore = + boundaryTokens + countTokens(UNIFORM.expected.normalized); + const tokensAfter = + boundaryTokens + countTokens(UNIFORM.expected.encoded as string); + expect(stats).toStrictEqual({ + tokensBefore, + tokensAfter, + costSavings: tokensBefore - tokensAfter, + wasEffective: true, + hadToolResults: true, + }); + }); + + describe("exact ToolCompressionStats accounting matrix", () => { + // Unlike the OpenAI family, hadToolResults is true even for malformed + // content: Anthropic counts every non-error tool_result block it sees. + const rows: { + row: string; + entry: GoldenEntry; + compressed: boolean; + counted: boolean; + }[] = [ + { row: "malformed", entry: MALFORMED, compressed: false, counted: false }, + { + row: "ineffective (rejected: original counted in both totals)", + entry: NEAR_BOUNDARY, + compressed: false, + counted: true, + }, + { row: "effective", entry: UNIFORM, compressed: true, counted: true }, + { + row: "wrapped-array", + entry: WRAPPED, + compressed: true, + counted: true, + }, + ]; + + for (const { row, entry, compressed, counted } of rows) { + test(row, async () => { + await upsertOneDollarPerTokenPricing(); + + const adapter = anthropicAdapterFactory.createRequestAdapter({ + model: "claude-sonnet-4-5", + max_tokens: 1024, + messages: [ + { role: "user", content: "run the tool" }, + { + role: "user", + content: [ + { + type: "tool_result", + tool_use_id: "toolu_1", + content: entry.rawContent, + }, + ], + }, + ], + }); + const stats = await adapter.applyToonCompression("claude-sonnet-4-5"); + + const tokensBefore = counted + ? countTokens(entry.expected.normalized) + : 0; + const tokensAfter = compressed + ? countTokens(entry.expected.encoded as string) + : tokensBefore; + expect(stats).toStrictEqual({ + tokensBefore, + tokensAfter, + costSavings: tokensBefore - tokensAfter, + wasEffective: compressed, + hadToolResults: true, + }); + + const [, toolMessage] = adapter.getProviderMessages(); + expect(toolMessage).toStrictEqual({ + role: "user", + content: [ + { + type: "tool_result", + tool_use_id: "toolu_1", + content: compressed + ? (entry.expected.encoded as string) + : entry.rawContent, + }, + ], + }); + }); + } + }); +}); diff --git a/platform/backend/src/routes/proxy/adapters/anthropic.ts b/platform/backend/src/routes/proxy/adapters/anthropic.ts index 5bf27cb8449..9853cccca12 100644 --- a/platform/backend/src/routes/proxy/adapters/anthropic.ts +++ b/platform/backend/src/routes/proxy/adapters/anthropic.ts @@ -4,7 +4,6 @@ import { PROVIDER_BILLING_BLOCK_BODY, PROVIDER_BILLING_BLOCK_TITLE, } from "@archestra/shared"; -import { encode as toonEncode } from "@toon-format/toon"; import { get } from "lodash-es"; import { anthropicWorkloadIdentity } from "@/clients/anthropic-workload-identity"; import { @@ -39,7 +38,7 @@ import { isImageTooLarge, isMcpImageBlock, } from "../utils/mcp-image"; -import { unwrapToolContent } from "../utils/unwrap-tool-content"; +import { toonEncodeToolResults } from "../utils/toon-native"; // ============================================================================= // TYPE ALIASES @@ -929,10 +928,48 @@ export async function convertToolResultsToToon( let totalTokensBefore = 0; let totalTokensAfter = 0; - const result = messages.map((message) => { + // Collect candidates from BOTH content shapes first so the native + // unwrap→parse→encode transform runs once per request (batched, off the JS + // thread). Results are positional and reapplied via message/content/block + // locators kept on the TS side — tool_use_id is NOT unique (one tool_result + // can hold several text blocks), so it is carried for logging only. + type AnthropicMessageContentBlocks = Extract< + AnthropicMessages[number]["content"], + unknown[] + >; + type AnthropicToolResultBlock = Extract< + AnthropicMessageContentBlocks[number], + { type: "tool_result" } + >; + type AnthropicToolResultInnerBlocks = Extract< + AnthropicToolResultBlock["content"], + unknown[] + >; + type ToonCandidate = + | { + kind: "string"; + messageIndex: number; + blockIndex: number; + block: AnthropicToolResultBlock; + content: string; + } + | { + kind: "array-text"; + messageIndex: number; + blockIndex: number; + block: AnthropicToolResultBlock; + textIndex: number; + textBlock: Extract< + AnthropicToolResultInnerBlocks[number], + { type: "text" } + >; + text: string; + }; + const candidates: ToonCandidate[] = []; + messages.forEach((message, messageIndex) => { // Only process user messages with content arrays that contain tool_result blocks if (message.role === "user" && Array.isArray(message.content)) { - const updatedContent = message.content.map((contentBlock) => { + message.content.forEach((contentBlock, blockIndex) => { if (contentBlock.type === "tool_result" && !contentBlock.is_error) { toolResultCount++; logger.info( @@ -946,183 +983,212 @@ export async function convertToolResultsToToon( // Handle string content if (typeof contentBlock.content === "string") { - try { - // Unwrap any extra text block wrapping from clients - const unwrapped = unwrapToolContent(contentBlock.content); - const parsed = JSON.parse(unwrapped); - const noncompressed = unwrapped; - const compressed = toonEncode(parsed); - - // Count tokens for before and after - const tokensBefore = tokenizer.countTokens([ - { role: "user", content: noncompressed }, - ]); - const tokensAfter = tokenizer.countTokens([ - { role: "user", content: compressed }, - ]); - - // Always count tokens - totalTokensBefore += tokensBefore; - - // Only apply compression if it actually saves tokens - if (tokensAfter < tokensBefore) { - totalTokensAfter += tokensAfter; - - logger.info( - { - toolCallId: contentBlock.tool_use_id, - beforeLength: noncompressed.length, - afterLength: compressed.length, - tokensBefore, - tokensAfter, - toonPreview: compressed.substring(0, 150), - provider: "anthropic", - }, - "convertToolResultsToToon: compressed (string content)", - ); - logger.trace( - { - toolCallId: contentBlock.tool_use_id, - before: noncompressed, - after: compressed, - provider: "anthropic", - supposedToBeJson: parsed, - }, - "convertToolResultsToToon: before/after", - ); - - return { - ...contentBlock, - content: compressed, - }; - } - - // Compression not applied - count non-compressed tokens to track total tokens anyway - totalTokensAfter += tokensBefore; - logger.info( - { - toolCallId: contentBlock.tool_use_id, - tokensBefore, - tokensAfter, - provider: "anthropic", - }, - "Skipping TOON compression - compressed output has more tokens", - ); - return contentBlock; - } catch { - logger.info( - { - toolCallId: contentBlock.tool_use_id, - contentPreview: - typeof contentBlock.content === "string" - ? contentBlock.content.substring(0, 100) - : "non-string", - }, - "convertToolResultsToToon: skipping - string content is not JSON", - ); - return contentBlock; - } + candidates.push({ + kind: "string", + messageIndex, + blockIndex, + block: contentBlock, + content: contentBlock.content, + }); + return; } // Handle array content (content blocks format) if (Array.isArray(contentBlock.content)) { - const updatedBlocks = contentBlock.content.map((block) => { + contentBlock.content.forEach((block, textIndex) => { if (block.type === "text" && typeof block.text === "string") { - try { - // Unwrap any extra text block wrapping from clients - const unwrapped = unwrapToolContent(block.text); - // Try to parse as JSON - const parsed = JSON.parse(unwrapped); - const noncompressed = unwrapped; - const compressed = toonEncode(parsed); - - // Count tokens for before and after - const tokensBefore = tokenizer.countTokens([ - { role: "user", content: noncompressed }, - ]); - const tokensAfter = tokenizer.countTokens([ - { role: "user", content: compressed }, - ]); - - // Always count tokens - totalTokensBefore += tokensBefore; - - // Only apply compression if it actually saves tokens - if (tokensAfter < tokensBefore) { - totalTokensAfter += tokensAfter; - - logger.info( - { - toolCallId: contentBlock.tool_use_id, - beforeLength: noncompressed.length, - afterLength: compressed.length, - tokensBefore, - tokensAfter, - toonPreview: compressed.substring(0, 150), - }, - "convertToolResultsToToon: compressed (array content)", - ); - logger.trace( - { - toolCallId: contentBlock.tool_use_id, - before: noncompressed, - after: compressed, - provider: "anthropic", - supposedToBeJson: parsed, - }, - "convertToolResultsToToon: before/after", - ); - - return { - ...block, - text: compressed, - }; - } - - // Compression not applied - count non-compressed tokens to track total tokens anyway - totalTokensAfter += tokensBefore; - logger.info( - { - toolCallId: contentBlock.tool_use_id, - tokensBefore, - tokensAfter, - provider: "anthropic", - }, - "Skipping TOON compression - compressed output has more tokens", - ); - return block; - } catch { - // Not JSON, keep as-is - logger.info( - { - toolCallId: contentBlock.tool_use_id, - blockType: block.type, - textPreview: block.text?.substring(0, 100), - }, - "convertToolResultsToToon: skipping - content is not JSON", - ); - return block; - } + candidates.push({ + kind: "array-text", + messageIndex, + blockIndex, + block: contentBlock, + textIndex, + textBlock: block, + text: block.text, + }); } - return block; }); - - return { - ...contentBlock, - content: updatedBlocks, - }; } } - return contentBlock; }); + } + }); - return { - ...message, - content: updatedContent, + const encodedResults = + candidates.length > 0 + ? await toonEncodeToolResults( + candidates.map((candidate) => ({ + id: candidate.block.tool_use_id, + rawContent: + candidate.kind === "string" ? candidate.content : candidate.text, + unwrap: true, + })), + ) + : []; + + if (encodedResults === null) { + // Native addon unavailable: fail open — keep every message uncompressed + // and surface the explicit skip reason instead of fabricating stats. + return { + messages, + stats: { + tokensBefore: 0, + tokensAfter: 0, + costSavings: 0, + wasEffective: false, + hadToolResults: toolResultCount > 0, + skipReason: "addon_unavailable", + }, + }; + } + + const result = [...messages]; + // Clone-on-write: untouched messages/blocks keep their original objects. + const clonedMessageContent = new Map(); + const contentFor = (messageIndex: number) => { + let cloned = clonedMessageContent.get(messageIndex); + if (!cloned) { + const message = messages[messageIndex]; + cloned = [...(message.content as AnthropicMessageContentBlocks)]; + clonedMessageContent.set(messageIndex, cloned); + result[messageIndex] = { ...message, content: cloned }; + } + return cloned; + }; + // Several text blocks can live in ONE tool_result block; clone its inner + // content array once per POSITION (message index → block index) and reuse + // it. Keyed by position, not by the block object: the same object can be + // aliased at several positions and each occurrence needs its own clone. + const clonedToolResultContent = new Map< + number, + Map + >(); + const innerBlocksFor = (candidate: { + messageIndex: number; + blockIndex: number; + block: AnthropicToolResultBlock; + }) => { + let byBlockIndex = clonedToolResultContent.get(candidate.messageIndex); + if (!byBlockIndex) { + byBlockIndex = new Map(); + clonedToolResultContent.set(candidate.messageIndex, byBlockIndex); + } + let cloned = byBlockIndex.get(candidate.blockIndex); + if (!cloned) { + cloned = [...(candidate.block.content as AnthropicToolResultInnerBlocks)]; + byBlockIndex.set(candidate.blockIndex, cloned); + contentFor(candidate.messageIndex)[candidate.blockIndex] = { + ...candidate.block, + content: cloned, }; } + return cloned; + }; + + candidates.forEach((candidate, candidateIndex) => { + const { normalized, encoded: compressed } = encodedResults[candidateIndex]; + const contentBlock = candidate.block; + + if (compressed === null) { + if (candidate.kind === "string") { + logger.info( + { + toolCallId: contentBlock.tool_use_id, + contentPreview: candidate.content.substring(0, 100), + }, + "convertToolResultsToToon: skipping - string content is not JSON", + ); + } else { + // Not JSON, keep as-is + logger.info( + { + toolCallId: contentBlock.tool_use_id, + blockType: candidate.textBlock.type, + textPreview: candidate.text.substring(0, 100), + }, + "convertToolResultsToToon: skipping - content is not JSON", + ); + } + return; + } + + // Token accounting on the normalized (unwrapped) string, exactly as + // before — each text block is counted individually. + const tokensBefore = tokenizer.countTokens([ + { role: "user", content: normalized }, + ]); + const tokensAfter = tokenizer.countTokens([ + { role: "user", content: compressed }, + ]); + + // Always count tokens + totalTokensBefore += tokensBefore; + + // Only apply compression if it actually saves tokens + if (tokensAfter < tokensBefore) { + totalTokensAfter += tokensAfter; + + if (candidate.kind === "string") { + logger.info( + { + toolCallId: contentBlock.tool_use_id, + beforeLength: normalized.length, + afterLength: compressed.length, + tokensBefore, + tokensAfter, + toonPreview: compressed.substring(0, 150), + provider: "anthropic", + }, + "convertToolResultsToToon: compressed (string content)", + ); + } else { + logger.info( + { + toolCallId: contentBlock.tool_use_id, + beforeLength: normalized.length, + afterLength: compressed.length, + tokensBefore, + tokensAfter, + toonPreview: compressed.substring(0, 150), + }, + "convertToolResultsToToon: compressed (array content)", + ); + } + logger.trace( + { + toolCallId: contentBlock.tool_use_id, + before: normalized, + after: compressed, + provider: "anthropic", + }, + "convertToolResultsToToon: before/after", + ); + + if (candidate.kind === "string") { + contentFor(candidate.messageIndex)[candidate.blockIndex] = { + ...contentBlock, + content: compressed, + }; + } else { + innerBlocksFor(candidate)[candidate.textIndex] = { + ...candidate.textBlock, + text: compressed, + }; + } + return; + } - return message; + // Compression not applied - count non-compressed tokens to track total tokens anyway + totalTokensAfter += tokensBefore; + logger.info( + { + toolCallId: contentBlock.tool_use_id, + tokensBefore, + tokensAfter, + provider: "anthropic", + }, + "Skipping TOON compression - compressed output has more tokens", + ); }); logger.info( diff --git a/platform/backend/src/routes/proxy/adapters/bedrock-toon-compression.test.ts b/platform/backend/src/routes/proxy/adapters/bedrock-toon-compression.test.ts new file mode 100644 index 00000000000..f88f4d59abc --- /dev/null +++ b/platform/backend/src/routes/proxy/adapters/bedrock-toon-compression.test.ts @@ -0,0 +1,539 @@ +// Pins the Bedrock adapter's TOON compression cutover to the native addon: +// full transformed-request exact equality (TOON content from the committed v3 +// golden corpus, everything else byte-equal) and the exact +// ToolCompressionStats accounting matrix. Bedrock-specific semantics pinned +// here: compression is applied UNCONDITIONALLY (no keep/reject — encoded +// tokens are always recorded, even when TOON is larger), NEITHER branch +// unwraps client wrappers, only content[0] of a toolResult is read, a +// compressed result replaces the WHOLE content array with one text item (the +// json branch is rewritten to text too), and error-status results are skipped +// entirely. Requires the built addon: mandatory in CI; locally it skips +// visibly — run `pnpm test:native` from platform/backend. + +import { readFileSync } from "node:fs"; +import path from "node:path"; +import { ModelModel } from "@/models"; +import { describe, expect, test } from "@/test"; +import { getTokenizer } from "@/tokenizers"; +import type { Bedrock } from "@/types"; +import { bedrockAdapterFactory } from "./bedrock"; + +type BedrockRequest = Bedrock.Types.ConverseRequest; + +type GoldenEntry = { + name: string; + rawContent: string; + unwrap: boolean; + expected: { normalized: string; encoded: string | null }; +}; + +const CORPUS_PATH = path.resolve( + import.meta.dirname, + "../../../../../archestra-rs/proxy-transform-core/tests/fixtures/golden-corpus.json", +); +const corpus: GoldenEntry[] = JSON.parse(readFileSync(CORPUS_PATH, "utf8")); +function corpusEntry(name: string): GoldenEntry { + const entry = corpus.find((candidate) => candidate.name === name); + if (!entry) throw new Error(`golden corpus entry not found: ${name}`); + return entry; +} + +// Corpus picks: a uniform array (TOON smaller), malformed JSON (kept as-is), +// a json-branch twin of the uniform array, and a wrapped [{type:"text",...}] +// payload encoded WITHOUT unwrapping (its TOON is the wrapper array itself +// and is LARGER than the original under the Anthropic tokenizer — Bedrock +// still applies it). +const UNIFORM = corpusEntry("provider-matrix-tool-result"); +const MALFORMED = corpusEntry("malformed-prose"); +const JSON_BRANCH = corpusEntry("bedrock-json-branch"); +const WRAPPED_NO_UNWRAP = corpusEntry("wrapped-but-unwrap-false"); +// A json-branch object whose TOON encoding is LARGER under the Anthropic +// tokenizer — Bedrock still applies it (unconditional apply on both branches). +const JSON_LARGER = corpusEntry("boundary-obj-3"); + +// Bedrock accounting uses the Anthropic tokenizer as an approximation. +const tokenizer = getTokenizer("anthropic"); +const countTokens = (content: string) => + tokenizer.countTokens([{ role: "user", content }]); +// The json branch tokenizes the adapter's own serialization of the json +// value — recompute it here instead of trusting the corpus rawContent. +const jsonBranchSerialized = JSON.stringify(JSON.parse(JSON_BRANCH.rawContent)); + +const BEDROCK_MODEL = "anthropic.claude-sonnet-4-5-20250929-v1:0"; + +// $1,000,000 per million input tokens = $1 per token, so expected costSavings +// equals tokens saved exactly. +async function upsertOneDollarPerTokenPricing() { + await ModelModel.upsert({ + externalId: `bedrock/${BEDROCK_MODEL}`, + provider: "bedrock", + modelId: BEDROCK_MODEL, + inputModalities: null, + outputModalities: null, + customPricePerMillionInput: "1000000.00", + customPricePerMillionOutput: "1000000.00", + lastSyncedAt: new Date(), + }); +} + +const addonLoadError: unknown = await import( + "@archestra/proxy-transform-rs" +).then( + () => null, + (error) => error, +); + +// In CI the suite must FAIL (never skip) when the addon is missing. +const describeNative = + addonLoadError === null || process.env.CI ? describe : describe.skip; +if (addonLoadError !== null && !process.env.CI) { + console.warn( + `[bedrock-toon-compression.test] skipping: @archestra/proxy-transform-rs is not built (${String( + addonLoadError, + )}). Run \`pnpm test:native\` from platform/backend.`, + ); +} + +describeNative("Bedrock adapter TOON compression (native addon)", () => { + test("transforms the full provider request exactly (goldens for TOON, byte-equal elsewhere)", async () => { + await upsertOneDollarPerTokenPricing(); + + const makeRequest = (): BedrockRequest => ({ + modelId: BEDROCK_MODEL, + inferenceConfig: { temperature: 0.25 }, + messages: [ + { + role: "user", + content: [{ text: "What files are in the current directory?" }], + }, + { + role: "assistant", + content: [ + { + toolUse: { + toolUseId: "tooluse_list", + name: "list_files", + input: { directory: "." }, + }, + }, + ], + }, + { + role: "user", + content: [ + { + toolResult: { + toolUseId: "tooluse_text", + content: [{ text: UNIFORM.rawContent }], + status: "success", + }, + }, + { + toolResult: { + toolUseId: "tooluse_json", + content: [{ json: JSON.parse(JSON_BRANCH.rawContent) }], + }, + }, + { + toolResult: { + toolUseId: "tooluse_malformed", + content: [{ text: MALFORMED.rawContent }], + }, + }, + ], + }, + ], + }); + + const adapter = bedrockAdapterFactory.createRequestAdapter(makeRequest()); + const stats = await adapter.applyToonCompression(BEDROCK_MODEL); + + // Both the text and json branches are rewritten to a single text item + // (the whole content array is replaced and toolResult fields like + // `status` are preserved by the spread); malformed content is kept. + const expectedRequest = makeRequest(); + // biome-ignore lint/style/noNonNullAssertion: fixture shape is static + expectedRequest.messages![2].content = [ + { + toolResult: { + toolUseId: "tooluse_text", + content: [{ text: UNIFORM.expected.encoded as string }], + status: "success", + }, + }, + { + toolResult: { + toolUseId: "tooluse_json", + content: [{ text: JSON_BRANCH.expected.encoded as string }], + }, + }, + { + toolResult: { + toolUseId: "tooluse_malformed", + content: [{ text: MALFORMED.rawContent }], + }, + }, + ]; + expect(adapter.toProviderRequest()).toStrictEqual(expectedRequest); + + // Unconditional accounting: both branches count original serialization + // vs encoded; malformed content is not counted at all. + const tokensBefore = + countTokens(UNIFORM.rawContent) + countTokens(jsonBranchSerialized); + const tokensAfter = + countTokens(UNIFORM.expected.encoded as string) + + countTokens(JSON_BRANCH.expected.encoded as string); + expect(stats).toStrictEqual({ + tokensBefore, + tokensAfter, + costSavings: tokensBefore - tokensAfter, + wasEffective: true, + hadToolResults: true, + }); + }); + + test("applies native results to the right blocks when non-candidate blocks are interleaved", async () => { + await upsertOneDollarPerTokenPricing(); + + // Non-candidates between candidates: a plain text block, an error-status + // toolResult (whose content WOULD compress if wrongly collected), an + // empty-content toolResult, and a non-user assistant turn. The winning + // candidate sits at native index 1, message index 2, block index 0 — all + // three DIFFER, so applying the native result by any wrong index + // (candidate-as-message, candidate-as-block, ...) compresses the wrong + // block and fails the equality below. + const makeRequest = (): BedrockRequest => ({ + modelId: BEDROCK_MODEL, + messages: [ + { + role: "user", + content: [ + { text: "status update" }, + { + toolResult: { + toolUseId: "tooluse_error", + content: [{ text: UNIFORM.rawContent }], + status: "error", + }, + }, + { + toolResult: { + toolUseId: "tooluse_empty", + content: [], + }, + }, + { + // native index 0 — malformed, kept as-is + toolResult: { + toolUseId: "tooluse_malformed", + content: [{ text: MALFORMED.rawContent }], + }, + }, + ], + }, + { role: "assistant", content: [{ text: "Let me check." }] }, + { + role: "user", + content: [ + { + // native index 1 — the winner + toolResult: { + toolUseId: "tooluse_ok", + content: [{ text: UNIFORM.rawContent }], + }, + }, + ], + }, + ], + }); + + const adapter = bedrockAdapterFactory.createRequestAdapter(makeRequest()); + const stats = await adapter.applyToonCompression(BEDROCK_MODEL); + + const expectedRequest = makeRequest(); + // biome-ignore lint/style/noNonNullAssertion: fixture shape is static + expectedRequest.messages![2].content[0] = { + toolResult: { + toolUseId: "tooluse_ok", + content: [{ text: UNIFORM.expected.encoded as string }], + }, + }; + expect(adapter.toProviderRequest()).toStrictEqual(expectedRequest); + + const tokensBefore = countTokens(UNIFORM.rawContent); + const tokensAfter = countTokens(UNIFORM.expected.encoded as string); + expect(stats).toStrictEqual({ + tokensBefore, + tokensAfter, + costSavings: tokensBefore - tokensAfter, + wasEffective: true, + hadToolResults: true, + }); + }); + + test("reads only content[0] and replaces the whole content array (multi-item content)", async () => { + await upsertOneDollarPerTokenPricing(); + + const makeRequest = (): BedrockRequest => ({ + modelId: BEDROCK_MODEL, + messages: [ + { + role: "user", + content: [ + { + toolResult: { + toolUseId: "tooluse_multi", + content: [ + { text: UNIFORM.rawContent }, + { text: "a second item that is silently dropped" }, + ], + }, + }, + { + toolResult: { + toolUseId: "tooluse_first_malformed", + content: [ + { text: MALFORMED.rawContent }, + // Compressible, but never looked at: content[0] decides. + { json: JSON.parse(JSON_BRANCH.rawContent) }, + ], + }, + }, + ], + }, + ], + }); + + const adapter = bedrockAdapterFactory.createRequestAdapter(makeRequest()); + const stats = await adapter.applyToonCompression(BEDROCK_MODEL); + + const expectedRequest = makeRequest(); + // biome-ignore lint/style/noNonNullAssertion: fixture shape is static + expectedRequest.messages![0].content[0] = { + toolResult: { + toolUseId: "tooluse_multi", + // The whole multi-item content collapses to one text item. + content: [{ text: UNIFORM.expected.encoded as string }], + }, + }; + expect(adapter.toProviderRequest()).toStrictEqual(expectedRequest); + + const tokensBefore = countTokens(UNIFORM.rawContent); + const tokensAfter = countTokens(UNIFORM.expected.encoded as string); + expect(stats).toStrictEqual({ + tokensBefore, + tokensAfter, + costSavings: tokensBefore - tokensAfter, + wasEffective: true, + hadToolResults: true, + }); + }); + + describe("exact ToolCompressionStats accounting matrix", () => { + // hadToolResults is true even for malformed content: Bedrock counts every + // non-error toolResult block it sees. There is no reject rule — when the + // encoding is larger it is still applied and both totals recorded. + const rows: { + row: string; + entry: GoldenEntry; + compressed: boolean; + counted: boolean; + }[] = [ + { row: "malformed", entry: MALFORMED, compressed: false, counted: false }, + { + row: "unconditional apply (encoded larger, no unwrap, still applied)", + entry: WRAPPED_NO_UNWRAP, + compressed: true, + counted: true, + }, + { row: "effective", entry: UNIFORM, compressed: true, counted: true }, + ]; + + for (const { row, entry, compressed, counted } of rows) { + test(row, async () => { + await upsertOneDollarPerTokenPricing(); + + const adapter = bedrockAdapterFactory.createRequestAdapter({ + modelId: BEDROCK_MODEL, + messages: [ + { role: "user", content: [{ text: "run the tool" }] }, + { + role: "user", + content: [ + { + toolResult: { + toolUseId: "tooluse_1", + content: [{ text: entry.rawContent }], + }, + }, + ], + }, + ], + }); + const stats = await adapter.applyToonCompression(BEDROCK_MODEL); + + const tokensBefore = counted ? countTokens(entry.rawContent) : 0; + const tokensAfter = compressed + ? countTokens(entry.expected.encoded as string) + : tokensBefore; + expect(stats).toStrictEqual({ + tokensBefore, + tokensAfter, + costSavings: Math.max(0, tokensBefore - tokensAfter), + wasEffective: tokensAfter < tokensBefore, + hadToolResults: true, + }); + + const [, toolMessage] = adapter.getProviderMessages(); + expect(toolMessage).toStrictEqual({ + role: "user", + content: [ + { + toolResult: { + toolUseId: "tooluse_1", + content: [ + { + text: compressed + ? (entry.expected.encoded as string) + : entry.rawContent, + }, + ], + }, + }, + ], + }); + }); + } + + test("json branch (encodes the json value, unconditional apply)", async () => { + await upsertOneDollarPerTokenPricing(); + + const adapter = bedrockAdapterFactory.createRequestAdapter({ + modelId: BEDROCK_MODEL, + messages: [ + { role: "user", content: [{ text: "run the tool" }] }, + { + role: "user", + content: [ + { + toolResult: { + toolUseId: "tooluse_json", + content: [{ json: JSON.parse(JSON_BRANCH.rawContent) }], + }, + }, + ], + }, + ], + }); + const stats = await adapter.applyToonCompression(BEDROCK_MODEL); + + const tokensBefore = countTokens(jsonBranchSerialized); + const tokensAfter = countTokens(JSON_BRANCH.expected.encoded as string); + expect(stats).toStrictEqual({ + tokensBefore, + tokensAfter, + costSavings: tokensBefore - tokensAfter, + wasEffective: true, + hadToolResults: true, + }); + + const [, toolMessage] = adapter.getProviderMessages(); + expect(toolMessage).toStrictEqual({ + role: "user", + content: [ + { + toolResult: { + toolUseId: "tooluse_json", + content: [{ text: JSON_BRANCH.expected.encoded as string }], + }, + }, + ], + }); + }); + + test("json branch unconditional apply (encoded larger, still applied)", async () => { + await upsertOneDollarPerTokenPricing(); + + const jsonLargerSerialized = JSON.stringify( + JSON.parse(JSON_LARGER.rawContent), + ); + const adapter = bedrockAdapterFactory.createRequestAdapter({ + modelId: BEDROCK_MODEL, + messages: [ + { role: "user", content: [{ text: "run the tool" }] }, + { + role: "user", + content: [ + { + toolResult: { + toolUseId: "tooluse_json_larger", + content: [{ json: JSON.parse(JSON_LARGER.rawContent) }], + }, + }, + ], + }, + ], + }); + const stats = await adapter.applyToonCompression(BEDROCK_MODEL); + + const tokensBefore = countTokens(jsonLargerSerialized); + const tokensAfter = countTokens(JSON_LARGER.expected.encoded as string); + expect(tokensAfter).toBeGreaterThan(tokensBefore); + expect(stats).toStrictEqual({ + tokensBefore, + tokensAfter, + costSavings: 0, + wasEffective: false, + hadToolResults: true, + }); + + // Still applied despite being larger — no keep/reject on this branch. + const [, toolMessage] = adapter.getProviderMessages(); + expect(toolMessage).toStrictEqual({ + role: "user", + content: [ + { + toolResult: { + toolUseId: "tooluse_json_larger", + content: [{ text: JSON_LARGER.expected.encoded as string }], + }, + }, + ], + }); + }); + + test("error-status result is skipped entirely (not counted as a tool result)", async () => { + await upsertOneDollarPerTokenPricing(); + + const makeRequest = (): BedrockRequest => ({ + modelId: BEDROCK_MODEL, + messages: [ + { + role: "user", + content: [ + { + toolResult: { + toolUseId: "tooluse_error", + content: [{ text: UNIFORM.rawContent }], + status: "error", + }, + }, + ], + }, + ], + }); + + const adapter = bedrockAdapterFactory.createRequestAdapter(makeRequest()); + const stats = await adapter.applyToonCompression(BEDROCK_MODEL); + + expect(adapter.toProviderRequest()).toStrictEqual(makeRequest()); + expect(stats).toStrictEqual({ + tokensBefore: 0, + tokensAfter: 0, + costSavings: 0, + wasEffective: false, + hadToolResults: false, + }); + }); + }); +}); diff --git a/platform/backend/src/routes/proxy/adapters/bedrock.ts b/platform/backend/src/routes/proxy/adapters/bedrock.ts index 9db6ea17e2a..b00bbd8fd08 100644 --- a/platform/backend/src/routes/proxy/adapters/bedrock.ts +++ b/platform/backend/src/routes/proxy/adapters/bedrock.ts @@ -6,7 +6,6 @@ import { import type { ConverseStreamOutput } from "@aws-sdk/client-bedrock-runtime"; import { EventStreamCodec } from "@smithy/eventstream-codec"; import { fromUtf8, toUtf8 } from "@smithy/util-utf8"; -import { encode as toonEncode } from "@toon-format/toon"; import { BedrockClient } from "@/clients/bedrock-client"; import { decodeBedrockSigV4Marker, @@ -35,6 +34,7 @@ import type { UsageView, } from "@/types"; import { extractCommonMessageText } from "@/types"; +import { toonEncodeToolResults } from "../utils/toon-native"; // ToolCompressionStats imported from @/types @@ -1426,10 +1426,27 @@ export async function convertToolResultsToToon( let totalTokensBefore = 0; let totalTokensAfter = 0; - const result = messages.map((message) => { + // Collect candidate tool results first so the native parse→encode transform + // runs once per request (batched, off the JS thread); results are positional + // and reapplied via message/content-block locators. Preserved semantics: + // only content[0] is read, a compressed result replaces the WHOLE content + // array, neither branch unwraps client wrappers (unwrap: false), and + // compression is applied unconditionally (no keep/reject rule). + type BedrockToolResult = Extract< + Bedrock.Types.Message["content"][number], + { toolResult: unknown } + >["toolResult"]; + const candidates: { + messageIndex: number; + blockIndex: number; + branch: "text" | "json"; + toolResult: BedrockToolResult; + rawContent: string; + }[] = []; + messages.forEach((message, messageIndex) => { // Only process user messages with content arrays that contain tool_result blocks if (message.role === "user" && Array.isArray(message.content)) { - const updatedContent = message.content.map((contentBlock) => { + message.content.forEach((contentBlock, blockIndex) => { if ( isToolResultBlock(contentBlock) && contentBlock.toolResult.status !== "error" @@ -1445,85 +1462,131 @@ export async function convertToolResultsToToon( "text" in firstContent && typeof firstContent.text === "string" ) { - try { - const parsed = JSON.parse(firstContent.text); - const noncompressed = firstContent.text; - const compressed = toonEncode(parsed); - - // Count tokens for before and after - const tokensBefore = tokenizer.countTokens([ - { role: "user", content: noncompressed }, - ]); - const tokensAfter = tokenizer.countTokens([ - { role: "user", content: compressed }, - ]); - totalTokensBefore += tokensBefore; - totalTokensAfter += tokensAfter; - - logger.info( - { - toolUseId: toolResult.toolUseId, - beforeLength: noncompressed.length, - afterLength: compressed.length, - tokensBefore, - tokensAfter, - provider: "bedrock", - }, - "convertToolResultsToToon: compressed", - ); - - return { - toolResult: { - ...toolResult, - content: [{ text: compressed }], - }, - }; - } catch { - logger.info( - { - toolUseId: toolResult.toolUseId, - }, - "convertToolResultsToToon: skipping - content is not JSON", - ); - return contentBlock; - } + candidates.push({ + messageIndex, + blockIndex, + branch: "text", + toolResult, + rawContent: firstContent.text, + }); } else if ("json" in firstContent && firstContent.json) { try { - const noncompressed = JSON.stringify(firstContent.json); - const compressed = toonEncode(firstContent.json); - - const tokensBefore = tokenizer.countTokens([ - { role: "user", content: noncompressed }, - ]); - const tokensAfter = tokenizer.countTokens([ - { role: "user", content: compressed }, - ]); - totalTokensBefore += tokensBefore; - totalTokensAfter += tokensAfter; - - return { - toolResult: { - ...toolResult, - content: [{ text: compressed }], - }, - }; + // JSON.stringify is typed as returning string but can yield + // undefined at runtime (e.g. a toJSON returning undefined); + // the old TS path then TOON-encoded the value as "null" and + // continued. Keep that, and never let a non-string poison the + // whole native batch. + const serialized: string | undefined = JSON.stringify( + firstContent.json, + ); + candidates.push({ + messageIndex, + blockIndex, + branch: "json", + toolResult, + rawContent: + typeof serialized === "string" ? serialized : "null", + }); } catch { - return contentBlock; + // Unstringifiable json content is kept as-is (silently, as before). } } } } - return contentBlock; }); + } + }); - return { - ...message, - content: updatedContent, - }; + const encodedResults = + candidates.length > 0 + ? await toonEncodeToolResults( + candidates.map((candidate) => ({ + id: candidate.toolResult.toolUseId, + rawContent: candidate.rawContent, + unwrap: false, + })), + ) + : []; + + if (encodedResults === null) { + // Native addon unavailable: fail open — keep every message uncompressed + // and surface the explicit skip reason instead of fabricating stats. + return { + messages, + stats: { + tokensBefore: 0, + tokensAfter: 0, + costSavings: 0, + wasEffective: false, + hadToolResults: toolResultCount > 0, + skipReason: "addon_unavailable", + }, + }; + } + + const result = [...messages]; + // Clone a message's content array on first write so untouched messages keep + // their original objects. + const clonedContent = new Map(); + const contentFor = (messageIndex: number) => { + let cloned = clonedContent.get(messageIndex); + if (!cloned) { + const message = messages[messageIndex]; + cloned = [...message.content]; + clonedContent.set(messageIndex, cloned); + result[messageIndex] = { ...message, content: cloned }; } + return cloned; + }; - return message; - }) as BedrockMessages; + candidates.forEach((candidate, candidateIndex) => { + const { encoded: compressed } = encodedResults[candidateIndex]; + const { toolResult, rawContent } = candidate; + + if (compressed === null) { + if (candidate.branch === "text") { + logger.info( + { + toolUseId: toolResult.toolUseId, + }, + "convertToolResultsToToon: skipping - content is not JSON", + ); + } + // json branch: kept as-is silently, as before. + return; + } + + // Token accounting on the original serialization (no unwrap on either branch). + const tokensBefore = tokenizer.countTokens([ + { role: "user", content: rawContent }, + ]); + const tokensAfter = tokenizer.countTokens([ + { role: "user", content: compressed }, + ]); + totalTokensBefore += tokensBefore; + totalTokensAfter += tokensAfter; + + if (candidate.branch === "text") { + logger.info( + { + toolUseId: toolResult.toolUseId, + beforeLength: rawContent.length, + afterLength: compressed.length, + tokensBefore, + tokensAfter, + provider: "bedrock", + }, + "convertToolResultsToToon: compressed", + ); + } + + contentFor(candidate.messageIndex)[candidate.blockIndex] = { + toolResult: { + ...toolResult, + content: [{ text: compressed }], + }, + }; + }); logger.info( { messageCount: messages.length, toolResultCount }, diff --git a/platform/backend/src/routes/proxy/adapters/cohere-toon-compression.test.ts b/platform/backend/src/routes/proxy/adapters/cohere-toon-compression.test.ts new file mode 100644 index 00000000000..304790c6c23 --- /dev/null +++ b/platform/backend/src/routes/proxy/adapters/cohere-toon-compression.test.ts @@ -0,0 +1,292 @@ +// Pins the Cohere adapter's TOON compression cutover to the native addon: +// full transformed-messages exact equality (TOON content from the committed +// v3 golden corpus, everything else byte-equal) and the exact +// ToolCompressionStats accounting matrix. Cohere-specific semantics pinned +// here: totals are updated ONLY on wins — a rejected (not smaller) payload +// contributes nothing to either total, so hadToolResults (totalTokensBefore > +// 0) is false when no result compresses. Requires the built addon: mandatory +// in CI; locally it skips visibly — run `pnpm test:native` from +// platform/backend. + +import { readFileSync } from "node:fs"; +import path from "node:path"; +import { ModelModel } from "@/models"; +import { describe, expect, test } from "@/test"; +import { getTokenizer } from "@/tokenizers"; +import type { Cohere } from "@/types"; +import { cohereAdapterFactory } from "./cohere"; + +type CohereRequest = Cohere.Types.ChatRequest; + +type GoldenEntry = { + name: string; + rawContent: string; + unwrap: boolean; + expected: { normalized: string; encoded: string | null }; +}; + +const CORPUS_PATH = path.resolve( + import.meta.dirname, + "../../../../../archestra-rs/proxy-transform-core/tests/fixtures/golden-corpus.json", +); +const corpus: GoldenEntry[] = JSON.parse(readFileSync(CORPUS_PATH, "utf8")); +function corpusEntry(name: string): GoldenEntry { + const entry = corpus.find((candidate) => candidate.name === name); + if (!entry) throw new Error(`golden corpus entry not found: ${name}`); + return entry; +} + +// Corpus picks: a uniform array (compression wins), a wrapped +// [{type:"text",...}] payload (unwrapped, compression wins), malformed JSON +// (kept as-is), and a near-boundary object whose TOON encoding does not save +// tokens under the Cohere tokenizer (rejected — and, per the wins-only rule, +// not counted at all). +const UNIFORM = corpusEntry("provider-matrix-tool-result"); +const WRAPPED = corpusEntry("wrapped-single-text"); +const MALFORMED = corpusEntry("malformed-prose"); +const NEAR_BOUNDARY = corpusEntry("boundary-obj-1"); + +const tokenizer = getTokenizer("cohere"); +const countTokens = (content: string) => + tokenizer.countTokens([{ role: "user", content }]); + +// $1,000,000 per million input tokens = $1 per token, so expected costSavings +// equals tokens saved exactly. +async function upsertOneDollarPerTokenPricing() { + await ModelModel.upsert({ + externalId: "cohere/command-r-plus", + provider: "cohere", + modelId: "command-r-plus", + inputModalities: null, + outputModalities: null, + customPricePerMillionInput: "1000000.00", + customPricePerMillionOutput: "1000000.00", + lastSyncedAt: new Date(), + }); +} + +const addonLoadError: unknown = await import( + "@archestra/proxy-transform-rs" +).then( + () => null, + (error) => error, +); + +// In CI the suite must FAIL (never skip) when the addon is missing. +const describeNative = + addonLoadError === null || process.env.CI ? describe : describe.skip; +if (addonLoadError !== null && !process.env.CI) { + console.warn( + `[cohere-toon-compression.test] skipping: @archestra/proxy-transform-rs is not built (${String( + addonLoadError, + )}). Run \`pnpm test:native\` from platform/backend.`, + ); +} + +function makeToolCall(id: string, name: string) { + return { + id, + type: "function" as const, + function: { name, arguments: '{"directory":"."}' }, + }; +} + +describeNative("Cohere adapter TOON compression (native addon)", () => { + test("transforms the full messages exactly (goldens for TOON, byte-equal elsewhere)", async () => { + await upsertOneDollarPerTokenPricing(); + + const makeRequest = (): CohereRequest => ({ + model: "command-r-plus", + temperature: 0.25, + messages: [ + { role: "system", content: "You are a filesystem assistant." }, + { role: "user", content: "What files are in the current directory?" }, + { + role: "assistant", + tool_calls: [ + makeToolCall("call_uniform", "list_files"), + makeToolCall("call_wrapped", "read_wrapped"), + makeToolCall("call_malformed", "read_notes"), + makeToolCall("call_boundary", "read_config"), + ], + }, + { + role: "tool", + tool_call_id: "call_uniform", + content: UNIFORM.rawContent, + }, + { + role: "tool", + tool_call_id: "call_wrapped", + content: WRAPPED.rawContent, + }, + { + role: "tool", + tool_call_id: "call_malformed", + content: MALFORMED.rawContent, + }, + { + role: "tool", + tool_call_id: "call_boundary", + content: NEAR_BOUNDARY.rawContent, + }, + ], + }); + + const adapter = cohereAdapterFactory.createRequestAdapter(makeRequest()); + const stats = await adapter.applyToonCompression("command-r-plus"); + + const expectedMessages = makeRequest().messages; + // Compression wins for the uniform array and the wrapped payload; the + // malformed and near-boundary results keep their original content. + expectedMessages[3] = { + role: "tool", + tool_call_id: "call_uniform", + content: UNIFORM.expected.encoded as string, + }; + expectedMessages[4] = { + role: "tool", + tool_call_id: "call_wrapped", + content: WRAPPED.expected.encoded as string, + }; + expect(adapter.getProviderMessages()).toStrictEqual(expectedMessages); + + // Wins-only accounting: the rejected near-boundary payload contributes + // NOTHING to either total (unlike the OpenAI-family rule); malformed + // content is not counted either. + const tokensBefore = + countTokens(UNIFORM.expected.normalized) + + countTokens(WRAPPED.expected.normalized); + const tokensAfter = + countTokens(UNIFORM.expected.encoded as string) + + countTokens(WRAPPED.expected.encoded as string); + expect(stats).toStrictEqual({ + tokensBefore, + tokensAfter, + costSavings: tokensBefore - tokensAfter, + wasEffective: true, + hadToolResults: true, + }); + }); + + test("applies native results to the right candidates when non-candidate messages are interleaved", async () => { + await upsertOneDollarPerTokenPricing(); + + // Cohere tool messages are string-only, so non-candidates here are + // assistant/user messages between tool messages: the native result index + // diverges from the message index — off-by-one positional application + // would compress the wrong message and fail the equality below. + const makeRequest = (): CohereRequest => ({ + model: "command-r-plus", + messages: [ + { role: "user", content: "Inspect the workspace." }, + { + role: "assistant", + tool_calls: [makeToolCall("call_a", "read_config")], + }, + { + role: "tool", + tool_call_id: "call_a", + content: NEAR_BOUNDARY.rawContent, + }, + { role: "assistant", content: "Let me look further." }, + { + role: "assistant", + tool_calls: [ + makeToolCall("call_b", "list_files"), + makeToolCall("call_c", "read_notes"), + ], + }, + { + role: "tool", + tool_call_id: "call_b", + content: UNIFORM.rawContent, + }, + { + role: "tool", + tool_call_id: "call_c", + content: MALFORMED.rawContent, + }, + ], + }); + + const adapter = cohereAdapterFactory.createRequestAdapter(makeRequest()); + const stats = await adapter.applyToonCompression("command-r-plus"); + + // Only B compresses; A is rejected (near-boundary), C is malformed. + const expectedMessages = makeRequest().messages; + expectedMessages[5] = { + role: "tool", + tool_call_id: "call_b", + content: UNIFORM.expected.encoded as string, + }; + expect(adapter.getProviderMessages()).toStrictEqual(expectedMessages); + + const tokensBefore = countTokens(UNIFORM.expected.normalized); + const tokensAfter = countTokens(UNIFORM.expected.encoded as string); + expect(stats).toStrictEqual({ + tokensBefore, + tokensAfter, + costSavings: tokensBefore - tokensAfter, + wasEffective: true, + hadToolResults: true, + }); + }); + + describe("exact ToolCompressionStats accounting matrix", () => { + // Wins-only accounting: rejected and malformed rows contribute nothing, + // so hadToolResults (totalTokensBefore > 0) is false for both. + const rows: { + row: string; + entry: GoldenEntry; + compressed: boolean; + }[] = [ + { row: "malformed", entry: MALFORMED, compressed: false }, + { + row: "ineffective (rejected: counted in NEITHER total)", + entry: NEAR_BOUNDARY, + compressed: false, + }, + { row: "effective", entry: UNIFORM, compressed: true }, + { row: "wrapped-array", entry: WRAPPED, compressed: true }, + ]; + + for (const { row, entry, compressed } of rows) { + test(row, async () => { + await upsertOneDollarPerTokenPricing(); + + const adapter = cohereAdapterFactory.createRequestAdapter({ + model: "command-r-plus", + messages: [ + { role: "user", content: "run the tool" }, + { role: "tool", tool_call_id: "call_1", content: entry.rawContent }, + ], + }); + const stats = await adapter.applyToonCompression("command-r-plus"); + + const tokensBefore = compressed + ? countTokens(entry.expected.normalized) + : 0; + const tokensAfter = compressed + ? countTokens(entry.expected.encoded as string) + : 0; + expect(stats).toStrictEqual({ + tokensBefore, + tokensAfter, + costSavings: tokensBefore - tokensAfter, + wasEffective: compressed, + hadToolResults: compressed, + }); + + const [, toolMessage] = adapter.getProviderMessages(); + expect(toolMessage).toStrictEqual({ + role: "tool", + tool_call_id: "call_1", + content: compressed + ? (entry.expected.encoded as string) + : entry.rawContent, + }); + }); + } + }); +}); diff --git a/platform/backend/src/routes/proxy/adapters/cohere.ts b/platform/backend/src/routes/proxy/adapters/cohere.ts index d023c06ba84..7eea658002f 100644 --- a/platform/backend/src/routes/proxy/adapters/cohere.ts +++ b/platform/backend/src/routes/proxy/adapters/cohere.ts @@ -1,6 +1,5 @@ import { randomUUID } from "node:crypto"; import type { ArchestraInternalErrorCode } from "@archestra/shared"; -import { encode as toonEncode } from "@toon-format/toon"; import { get } from "lodash-es"; import config from "@/config"; import logger from "@/logging"; @@ -25,7 +24,7 @@ import type { } from "@/types"; import { extractCommonMessageText } from "@/types"; import type { ToolCompressionStats as CompressionStats } from "../utils/toon-conversion"; -import { unwrapToolContent } from "../utils/unwrap-tool-content"; +import { toonEncodeToolResults } from "../utils/toon-native"; // ============================================================================= // TYPE ALIASES @@ -742,84 +741,107 @@ export async function convertToolResultsToToon( let totalTokensBefore = 0; let totalTokensAfter = 0; - const result = messages.map((message) => { + // Collect candidate tool messages first so the native unwrap→parse→encode + // transform runs once per request (batched, off the JS thread); results are + // positional and reapplied by message index. + const candidates: { + index: number; + message: Cohere.Types.ToolMessage; + }[] = []; + messages.forEach((message, index) => { if (message.role === "tool") { - const toolMsg = message as Cohere.Types.ToolMessage; - - try { - const unwrapped = unwrapToolContent(toolMsg.content); - const parsedRes = safeJsonParse(unwrapped); - if (!parsedRes.ok) { - logger.info( - { - toolCallId: toolMsg.tool_call_id, - contentPreview: toolMsg.content.substring(0, 100), - }, - "convertToolResultsToToon: skipping - content is not JSON", - ); - return message; - } + candidates.push({ index, message: message as Cohere.Types.ToolMessage }); + } + }); - const parsed = parsedRes.value as unknown; - const noncompressed = unwrapped; - const compressed = toonEncode(parsed); + const encodedResults = + candidates.length > 0 + ? await toonEncodeToolResults( + candidates.map((candidate) => ({ + id: candidate.message.tool_call_id, + rawContent: candidate.message.content, + unwrap: true, + })), + ) + : []; + + if (encodedResults === null) { + // Native addon unavailable: fail open — keep every message uncompressed + // and surface the explicit skip reason instead of fabricating stats. + return { + messages, + stats: { + tokensBefore: 0, + tokensAfter: 0, + costSavings: 0, + wasEffective: false, + hadToolResults: candidates.length > 0, + skipReason: "addon_unavailable", + }, + }; + } - const tokensBefore = tokenizer.countTokens([ - { role: "user", content: noncompressed }, - ]); - const tokensAfter = tokenizer.countTokens([ - { role: "user", content: compressed }, - ]); + const result = [...messages]; + candidates.forEach((candidate, candidateIndex) => { + const { normalized, encoded: compressed } = encodedResults[candidateIndex]; + const { message: toolMsg } = candidate; - // Only use TOON compression if it actually saves tokens - if (tokensAfter < tokensBefore) { - totalTokensBefore += tokensBefore; - totalTokensAfter += tokensAfter; + if (compressed === null) { + logger.info( + { + toolCallId: toolMsg.tool_call_id, + contentPreview: toolMsg.content.substring(0, 100), + }, + "convertToolResultsToToon: skipping - content is not JSON", + ); + return; + } - logger.info( - { - toolCallId: toolMsg.tool_call_id, - beforeLength: noncompressed.length, - afterLength: compressed.length, - tokensBefore, - tokensAfter, - tokensSaved: tokensBefore - tokensAfter, - provider: "cohere", - }, - "convertToolResultsToToon: compressed", - ); + // Token accounting on the normalized (unwrapped) string, exactly as before. + const tokensBefore = tokenizer.countTokens([ + { role: "user", content: normalized }, + ]); + const tokensAfter = tokenizer.countTokens([ + { role: "user", content: compressed }, + ]); + + // Only use TOON compression if it actually saves tokens — Cohere counts + // totals only for wins. + if (tokensAfter < tokensBefore) { + totalTokensBefore += tokensBefore; + totalTokensAfter += tokensAfter; + + logger.info( + { + toolCallId: toolMsg.tool_call_id, + beforeLength: normalized.length, + afterLength: compressed.length, + tokensBefore, + tokensAfter, + tokensSaved: tokensBefore - tokensAfter, + provider: "cohere", + }, + "convertToolResultsToToon: compressed", + ); - return { - ...toolMsg, - content: compressed, - }; - } else { - logger.info( - { - toolCallId: toolMsg.tool_call_id, - beforeLength: noncompressed.length, - afterLength: compressed.length, - tokensBefore, - tokensAfter, - tokensDiff: tokensAfter - tokensBefore, - provider: "cohere", - }, - "convertToolResultsToToon: skipping - compression increases tokens", - ); - return message; - } - } catch { - logger.info( - { - toolCallId: toolMsg.tool_call_id, - contentPreview: toolMsg.content.substring(0, 100), - }, - "convertToolResultsToToon: skipping - content is not JSON", - ); - return message; - } + result[candidate.index] = { + ...toolMsg, + content: compressed, + }; + } else { + logger.info( + { + toolCallId: toolMsg.tool_call_id, + beforeLength: normalized.length, + afterLength: compressed.length, + tokensBefore, + tokensAfter, + tokensDiff: tokensAfter - tokensBefore, + provider: "cohere", + }, + "convertToolResultsToToon: skipping - compression increases tokens", + ); } - return message; }); // Calculate cost savings diff --git a/platform/backend/src/routes/proxy/adapters/gemini-toon-compression.test.ts b/platform/backend/src/routes/proxy/adapters/gemini-toon-compression.test.ts new file mode 100644 index 00000000000..2e55923d133 --- /dev/null +++ b/platform/backend/src/routes/proxy/adapters/gemini-toon-compression.test.ts @@ -0,0 +1,336 @@ +// Pins the Gemini adapter's TOON compression cutover to the native addon: +// full transformed-contents exact equality (TOON content from the committed +// v3 golden corpus, everything else byte-equal) and the exact +// ToolCompressionStats accounting matrix. Gemini-specific semantics pinned +// here: the adapter serializes functionResponse.response itself and tokenizes +// that ORIGINAL serialization (not the unwrapped string) while parsing goes +// through the unwrap path, a winning part is replaced with +// { functionResponse: { ..., response: { tool_result: "" } } }, and +// rejected payloads count their original tokens in both totals. Requires the +// built addon: mandatory in CI; locally it skips visibly — run +// `pnpm test:native` from platform/backend. + +import { readFileSync } from "node:fs"; +import path from "node:path"; +import { ModelModel } from "@/models"; +import { describe, expect, test } from "@/test"; +import { getTokenizer } from "@/tokenizers"; +import type { Gemini } from "@/types"; +import { geminiAdapterFactory } from "./gemini"; + +type GeminiRequest = Gemini.Types.GenerateContentRequest; + +type GoldenEntry = { + name: string; + rawContent: string; + unwrap: boolean; + expected: { normalized: string; encoded: string | null }; +}; + +const CORPUS_PATH = path.resolve( + import.meta.dirname, + "../../../../../archestra-rs/proxy-transform-core/tests/fixtures/golden-corpus.json", +); +const corpus: GoldenEntry[] = JSON.parse(readFileSync(CORPUS_PATH, "utf8")); +function corpusEntry(name: string): GoldenEntry { + const entry = corpus.find((candidate) => candidate.name === name); + if (!entry) throw new Error(`golden corpus entry not found: ${name}`); + return entry; +} + +// Corpus picks: a uniform array (compression wins), a wrapped +// [{type:"text",...}] payload (unwrapped for parsing, compression wins), a +// wrapper whose inner text is NOT JSON (the "cannot be compressed" path — a +// response object always serializes to valid JSON, so this is Gemini's only +// unparseable case), and a near-boundary object whose TOON encoding does not +// save tokens under the Gemini tokenizer (rejected). +const UNIFORM = corpusEntry("provider-matrix-tool-result"); +const WRAPPED = corpusEntry("wrapped-single-text"); +const UNPARSEABLE = corpusEntry("wrapped-text-not-json"); +const NEAR_BOUNDARY = corpusEntry("boundary-obj-1"); + +const tokenizer = getTokenizer("gemini"); +const countTokens = (content: string) => + tokenizer.countTokens([{ role: "user", content }]); +// Gemini accounting uses the adapter's own serialization of the response +// object — recompute it here instead of trusting the corpus rawContent. +const serialized = (entry: GoldenEntry) => + JSON.stringify(JSON.parse(entry.rawContent)); + +// $1,000,000 per million input tokens = $1 per token, so expected costSavings +// equals tokens saved exactly. +async function upsertOneDollarPerTokenPricing() { + await ModelModel.upsert({ + externalId: "gemini/gemini-2.0-flash", + provider: "gemini", + modelId: "gemini-2.0-flash", + inputModalities: null, + outputModalities: null, + customPricePerMillionInput: "1000000.00", + customPricePerMillionOutput: "1000000.00", + lastSyncedAt: new Date(), + }); +} + +const addonLoadError: unknown = await import( + "@archestra/proxy-transform-rs" +).then( + () => null, + (error) => error, +); + +// In CI the suite must FAIL (never skip) when the addon is missing. +const describeNative = + addonLoadError === null || process.env.CI ? describe : describe.skip; +if (addonLoadError !== null && !process.env.CI) { + console.warn( + `[gemini-toon-compression.test] skipping: @archestra/proxy-transform-rs is not built (${String( + addonLoadError, + )}). Run \`pnpm test:native\` from platform/backend.`, + ); +} + +describeNative("Gemini adapter TOON compression (native addon)", () => { + test("transforms the full contents exactly (goldens for TOON, byte-equal elsewhere)", async () => { + await upsertOneDollarPerTokenPricing(); + + const makeRequest = (): GeminiRequest => ({ + contents: [ + { + role: "user", + parts: [{ text: "What files are in the current directory?" }], + }, + { role: "model", parts: [{ text: "Calling the tools now." }] }, + { + role: "user", + parts: [ + { + functionResponse: { + id: "fc_uniform", + name: "list_files", + response: JSON.parse(UNIFORM.rawContent), + }, + }, + { + functionResponse: { + name: "read_wrapped", + response: JSON.parse(WRAPPED.rawContent), + }, + }, + { + functionResponse: { + name: "read_notes", + response: JSON.parse(UNPARSEABLE.rawContent), + }, + }, + { + functionResponse: { + name: "read_config", + response: JSON.parse(NEAR_BOUNDARY.rawContent), + }, + }, + ], + }, + ], + }); + + const adapter = geminiAdapterFactory.createRequestAdapter(makeRequest()); + const stats = await adapter.applyToonCompression("gemini-2.0-flash"); + + // Compression wins for the uniform array and the wrapped payload (the + // functionResponse spread keeps `id`); the unparseable and near-boundary + // responses keep their original parts. + const expectedContents = makeRequest().contents; + expectedContents[2].parts[0] = { + functionResponse: { + id: "fc_uniform", + name: "list_files", + response: { tool_result: UNIFORM.expected.encoded as string }, + }, + }; + expectedContents[2].parts[1] = { + functionResponse: { + name: "read_wrapped", + response: { tool_result: WRAPPED.expected.encoded as string }, + }, + }; + expect(adapter.getProviderMessages()).toStrictEqual(expectedContents); + + // Token accounting uses the ORIGINAL serialization of each response — + // for the wrapped payload that is the whole wrapper array, not the + // unwrapped inner text (which would count fewer tokens). Rejected + // payloads count their original tokens in both totals; the unparseable + // response is not counted at all. + const boundaryTokens = countTokens(serialized(NEAR_BOUNDARY)); + const tokensBefore = + countTokens(serialized(UNIFORM)) + + countTokens(serialized(WRAPPED)) + + boundaryTokens; + const tokensAfter = + countTokens(UNIFORM.expected.encoded as string) + + countTokens(WRAPPED.expected.encoded as string) + + boundaryTokens; + expect(stats).toStrictEqual({ + tokensBefore, + tokensAfter, + costSavings: tokensBefore - tokensAfter, + wasEffective: true, + hadToolResults: true, + }); + }); + + test("applies native results to the right parts when non-candidate parts are interleaved", async () => { + await upsertOneDollarPerTokenPricing(); + + // Text parts and a model turn between functionResponse parts: the + // winning candidate sits at native index 2, content index 3, part index + // 1 — all three DIFFER, so applying the native result by any wrong index + // (candidate-as-part, candidate-as-content, ...) compresses the wrong + // part and fails the equality below. + const makeRequest = (): GeminiRequest => ({ + contents: [ + { role: "user", parts: [{ text: "Inspect the workspace." }] }, + { + role: "user", + parts: [ + { text: "intermediate status" }, + { + // native index 0 + functionResponse: { + name: "read_config", + response: JSON.parse(NEAR_BOUNDARY.rawContent), + }, + }, + { + // native index 1 + functionResponse: { + name: "read_notes", + response: JSON.parse(UNPARSEABLE.rawContent), + }, + }, + ], + }, + { role: "model", parts: [{ text: "Let me look further." }] }, + { + role: "user", + parts: [ + { text: "more status" }, + { + // native index 2 — the winner + functionResponse: { + name: "list_files", + response: JSON.parse(UNIFORM.rawContent), + }, + }, + ], + }, + ], + }); + + const adapter = geminiAdapterFactory.createRequestAdapter(makeRequest()); + const stats = await adapter.applyToonCompression("gemini-2.0-flash"); + + // Only the uniform response compresses; the near-boundary and + // unparseable responses and the text parts are untouched. + const expectedContents = makeRequest().contents; + expectedContents[3].parts[1] = { + functionResponse: { + name: "list_files", + response: { tool_result: UNIFORM.expected.encoded as string }, + }, + }; + expect(adapter.getProviderMessages()).toStrictEqual(expectedContents); + + const boundaryTokens = countTokens(serialized(NEAR_BOUNDARY)); + const tokensBefore = boundaryTokens + countTokens(serialized(UNIFORM)); + const tokensAfter = + boundaryTokens + countTokens(UNIFORM.expected.encoded as string); + expect(stats).toStrictEqual({ + tokensBefore, + tokensAfter, + costSavings: tokensBefore - tokensAfter, + wasEffective: true, + hadToolResults: true, + }); + }); + + describe("exact ToolCompressionStats accounting matrix", () => { + // hadToolResults is true even for the unparseable row: Gemini counts + // every functionResponse part it sees. + const rows: { + row: string; + entry: GoldenEntry; + compressed: boolean; + counted: boolean; + }[] = [ + { + row: "unparseable after unwrap", + entry: UNPARSEABLE, + compressed: false, + counted: false, + }, + { + row: "ineffective (rejected: original counted in both totals)", + entry: NEAR_BOUNDARY, + compressed: false, + counted: true, + }, + { row: "effective", entry: UNIFORM, compressed: true, counted: true }, + { + row: "wrapped-array (tokenized as the whole wrapper serialization)", + entry: WRAPPED, + compressed: true, + counted: true, + }, + ]; + + for (const { row, entry, compressed, counted } of rows) { + test(row, async () => { + await upsertOneDollarPerTokenPricing(); + + const makeContents = (): GeminiRequest["contents"] => [ + { role: "user", parts: [{ text: "run the tool" }] }, + { + role: "user", + parts: [ + { + functionResponse: { + name: "the_tool", + response: JSON.parse(entry.rawContent), + }, + }, + ], + }, + ]; + + const adapter = geminiAdapterFactory.createRequestAdapter({ + contents: makeContents(), + }); + const stats = await adapter.applyToonCompression("gemini-2.0-flash"); + + const tokensBefore = counted ? countTokens(serialized(entry)) : 0; + const tokensAfter = compressed + ? countTokens(entry.expected.encoded as string) + : tokensBefore; + expect(stats).toStrictEqual({ + tokensBefore, + tokensAfter, + costSavings: tokensBefore - tokensAfter, + wasEffective: compressed, + hadToolResults: true, + }); + + const expectedContents = makeContents(); + if (compressed) { + expectedContents[1].parts[0] = { + functionResponse: { + name: "the_tool", + response: { tool_result: entry.expected.encoded as string }, + }, + }; + } + expect(adapter.getProviderMessages()).toStrictEqual(expectedContents); + }); + } + }); +}); diff --git a/platform/backend/src/routes/proxy/adapters/gemini.ts b/platform/backend/src/routes/proxy/adapters/gemini.ts index ea4dee531c8..d47daaf9efb 100644 --- a/platform/backend/src/routes/proxy/adapters/gemini.ts +++ b/platform/backend/src/routes/proxy/adapters/gemini.ts @@ -10,7 +10,6 @@ import { type HarmProbability, type Part, } from "@google/genai"; -import { encode as toonEncode } from "@toon-format/toon"; import { get } from "lodash-es"; import { createGoogleGenAIClient } from "@/clients/gemini-client"; import config from "@/config"; @@ -40,7 +39,7 @@ import { isImageTooLarge, isMcpImageBlock, } from "../utils/mcp-image"; -import { unwrapToolContent } from "../utils/unwrap-tool-content"; +import { toonEncodeToolResults } from "../utils/toon-native"; import { sanitizeGeminiToolSchema } from "./gemini-schema"; // ============================================================================= @@ -872,10 +871,25 @@ async function convertToolResultsToToon( let totalTokensBefore = 0; let totalTokensAfter = 0; - const result = contents.map((content) => { + // Collect candidate functionResponse parts first so the native + // unwrap→parse→encode transform runs once per request (batched, off the JS + // thread); results are positional and reapplied via content/part locators. + // Gemini serializes the response object itself and tokenizes THAT original + // serialization (rawContent), while parsing goes through the unwrap path. + type GeminiFunctionResponsePart = Extract< + GeminiContents[number]["parts"][number], + { functionResponse: unknown } + >; + const candidates: { + contentIndex: number; + partIndex: number; + functionResponse: GeminiFunctionResponsePart["functionResponse"]; + rawContent: string; + }[] = []; + contents.forEach((content, contentIndex) => { // Only process user messages with parts containing functionResponse if (content.role === "user" && content.parts) { - const updatedParts = content.parts.map((part) => { + content.parts.forEach((part, partIndex) => { // Check if this part has a functionResponse if ( "functionResponse" in part && @@ -898,85 +912,24 @@ async function convertToolResultsToToon( // Handle response object - try to compress it const response = functionResponse.response; if (response && typeof response === "object") { + // JSON.stringify is typed as returning string but can throw or + // yield undefined at runtime (e.g. a toJSON returning undefined); + // a non-string must never reach the native binding — it would + // reject the WHOLE batch. Skip just this part, as before. + let rawContent: string | undefined; try { - const noncompressed = JSON.stringify(response); - const unwrapped = unwrapToolContent(noncompressed); - const parsed = JSON.parse(unwrapped); - const compressed = toonEncode(parsed); - - // Count tokens for before and after - const tokensBefore = tokenizer.countTokens([ - { role: "user", content: noncompressed }, - ]); - const tokensAfter = tokenizer.countTokens([ - { role: "user", content: compressed }, - ]); - - // Always count tokens - totalTokensBefore += tokensBefore; - - // Only apply compression if it actually saves tokens - if (tokensAfter < tokensBefore) { - totalTokensAfter += tokensAfter; - - logger.info( - { - functionName: - "name" in functionResponse - ? functionResponse.name - : "unknown", - beforeLength: noncompressed.length, - afterLength: compressed.length, - tokensBefore, - tokensAfter, - toonPreview: compressed.substring(0, 150), - provider: "gemini", - }, - "convertToolResultsToToon: compressed", - ); - logger.trace( - { - functionName: - "name" in functionResponse - ? functionResponse.name - : "unknown", - before: noncompressed, - after: compressed, - provider: "gemini", - }, - "convertToolResultsToToon: before/after", - ); - - // Return updated part with compressed response - return { - functionResponse: { - ...functionResponse, - // Gemini expects response as Record, but we now have a TOON string - // We wrap it in a {"tool_result": ""} object to match the expected format - response: { tool_result: compressed } as Record< - string, - unknown - >, - }, - }; - } - - // Compression not applied - count non-compressed tokens to track total tokens anyway - totalTokensAfter += tokensBefore; - logger.info( - { - functionName: - "name" in functionResponse - ? functionResponse.name - : "unknown", - tokensBefore, - tokensAfter, - provider: "gemini", - }, - "Skipping TOON compression - compressed output has more tokens", - ); - return part; + rawContent = JSON.stringify(response); } catch { + rawContent = undefined; + } + if (typeof rawContent === "string") { + candidates.push({ + contentIndex, + partIndex, + functionResponse, + rawContent, + }); + } else { logger.info( { functionName: @@ -986,20 +939,135 @@ async function convertToolResultsToToon( }, "convertToolResultsToToon: skipping - response cannot be compressed", ); - return part; } } } - return part; }); + } + }); - return { - ...content, - parts: updatedParts, + const encodedResults = + candidates.length > 0 + ? await toonEncodeToolResults( + candidates.map((candidate) => ({ + id: + "name" in candidate.functionResponse && + typeof candidate.functionResponse.name === "string" + ? candidate.functionResponse.name + : "unknown", + rawContent: candidate.rawContent, + unwrap: true, + })), + ) + : []; + + if (encodedResults === null) { + // Native addon unavailable: fail open — keep every content entry + // uncompressed and surface the explicit skip reason instead of + // fabricating stats. + return { + contents, + stats: { + tokensBefore: 0, + tokensAfter: 0, + costSavings: 0, + wasEffective: false, + hadToolResults: toolResultCount > 0, + skipReason: "addon_unavailable", + }, + }; + } + + const result = [...contents]; + // Clone a content entry's parts array on first write so untouched entries + // keep their original objects. + const clonedParts = new Map(); + const partsFor = (contentIndex: number) => { + let cloned = clonedParts.get(contentIndex); + if (!cloned) { + const content = contents[contentIndex]; + cloned = [...content.parts]; + clonedParts.set(contentIndex, cloned); + result[contentIndex] = { ...content, parts: cloned }; + } + return cloned; + }; + + candidates.forEach((candidate, candidateIndex) => { + const { encoded: compressed } = encodedResults[candidateIndex]; + const { functionResponse, rawContent: noncompressed } = candidate; + const functionName = + "name" in functionResponse ? functionResponse.name : "unknown"; + + if (compressed === null) { + logger.info( + { functionName }, + "convertToolResultsToToon: skipping - response cannot be compressed", + ); + return; + } + + // Token accounting on the ORIGINAL serialization (not the unwrapped + // string), exactly as before. + const tokensBefore = tokenizer.countTokens([ + { role: "user", content: noncompressed }, + ]); + const tokensAfter = tokenizer.countTokens([ + { role: "user", content: compressed }, + ]); + + // Always count tokens + totalTokensBefore += tokensBefore; + + // Only apply compression if it actually saves tokens + if (tokensAfter < tokensBefore) { + totalTokensAfter += tokensAfter; + + logger.info( + { + functionName, + beforeLength: noncompressed.length, + afterLength: compressed.length, + tokensBefore, + tokensAfter, + toonPreview: compressed.substring(0, 150), + provider: "gemini", + }, + "convertToolResultsToToon: compressed", + ); + logger.trace( + { + functionName, + before: noncompressed, + after: compressed, + provider: "gemini", + }, + "convertToolResultsToToon: before/after", + ); + + // Replace the part with the compressed response + partsFor(candidate.contentIndex)[candidate.partIndex] = { + functionResponse: { + ...functionResponse, + // Gemini expects response as Record, but we now have a TOON string + // We wrap it in a {"tool_result": ""} object to match the expected format + response: { tool_result: compressed } as Record, + }, }; + return; } - return content; + // Compression not applied - count non-compressed tokens to track total tokens anyway + totalTokensAfter += tokensBefore; + logger.info( + { + functionName, + tokensBefore, + tokensAfter, + provider: "gemini", + }, + "Skipping TOON compression - compressed output has more tokens", + ); }); logger.info( diff --git a/platform/backend/src/routes/proxy/adapters/minimax-toon-compression.test.ts b/platform/backend/src/routes/proxy/adapters/minimax-toon-compression.test.ts new file mode 100644 index 00000000000..758560d1d3e --- /dev/null +++ b/platform/backend/src/routes/proxy/adapters/minimax-toon-compression.test.ts @@ -0,0 +1,300 @@ +// Pins the MiniMax adapter's TOON compression cutover to the native addon: +// full transformed-request exact equality (TOON content from the committed v3 +// golden corpus, everything else byte-equal) and the exact +// ToolCompressionStats accounting matrix. MiniMax-specific semantics pinned +// here: compression is applied UNCONDITIONALLY (no keep/reject — encoded +// tokens are always recorded, even when TOON is larger). Requires the built +// addon: mandatory in CI; locally it skips visibly — run `pnpm test:native` +// from platform/backend. + +import { readFileSync } from "node:fs"; +import path from "node:path"; +import { ModelModel } from "@/models"; +import { describe, expect, test } from "@/test"; +import { getTokenizer } from "@/tokenizers"; +import type { Minimax } from "@/types/llm-providers"; +import { minimaxAdapterFactory } from "./minimax"; + +type MinimaxRequest = Minimax.Types.ChatCompletionsRequest; + +type GoldenEntry = { + name: string; + rawContent: string; + unwrap: boolean; + expected: { normalized: string; encoded: string | null }; +}; + +const CORPUS_PATH = path.resolve( + import.meta.dirname, + "../../../../../archestra-rs/proxy-transform-core/tests/fixtures/golden-corpus.json", +); +const corpus: GoldenEntry[] = JSON.parse(readFileSync(CORPUS_PATH, "utf8")); +function corpusEntry(name: string): GoldenEntry { + const entry = corpus.find((candidate) => candidate.name === name); + if (!entry) throw new Error(`golden corpus entry not found: ${name}`); + return entry; +} + +// Corpus picks: a uniform array (TOON smaller), a wrapped [{type:"text",...}] +// payload (unwrapped, TOON smaller), malformed JSON (kept as-is), and a +// heterogeneous array whose TOON encoding is LARGER under the MiniMax +// tokenizer — MiniMax still applies it (unconditional apply). +const UNIFORM = corpusEntry("provider-matrix-tool-result"); +const WRAPPED = corpusEntry("wrapped-single-text"); +const MALFORMED = corpusEntry("malformed-prose"); +const LARGER = corpusEntry("boundary-hetero-arr-1"); + +const tokenizer = getTokenizer("minimax"); +const countTokens = (content: string) => + tokenizer.countTokens([{ role: "user", content }]); + +// $1,000,000 per million input tokens = $1 per token, so expected costSavings +// equals tokens saved exactly. +async function upsertOneDollarPerTokenPricing() { + await ModelModel.upsert({ + externalId: "minimax/MiniMax-M2", + provider: "minimax", + modelId: "MiniMax-M2", + inputModalities: null, + outputModalities: null, + customPricePerMillionInput: "1000000.00", + customPricePerMillionOutput: "1000000.00", + lastSyncedAt: new Date(), + }); +} + +const addonLoadError: unknown = await import( + "@archestra/proxy-transform-rs" +).then( + () => null, + (error) => error, +); + +// In CI the suite must FAIL (never skip) when the addon is missing. +const describeNative = + addonLoadError === null || process.env.CI ? describe : describe.skip; +if (addonLoadError !== null && !process.env.CI) { + console.warn( + `[minimax-toon-compression.test] skipping: @archestra/proxy-transform-rs is not built (${String( + addonLoadError, + )}). Run \`pnpm test:native\` from platform/backend.`, + ); +} + +function makeToolCall(id: string, name: string) { + return { + id, + type: "function" as const, + function: { name, arguments: '{"directory":"."}' }, + }; +} + +describeNative("MiniMax adapter TOON compression (native addon)", () => { + test("transforms the full messages exactly (goldens for TOON, byte-equal elsewhere)", async () => { + await upsertOneDollarPerTokenPricing(); + + const makeRequest = (): MinimaxRequest => ({ + model: "MiniMax-M2", + temperature: 0.25, + messages: [ + { role: "system", content: "You are a filesystem assistant." }, + { role: "user", content: "What files are in the current directory?" }, + { + role: "assistant", + content: null, + tool_calls: [ + makeToolCall("call_uniform", "list_files"), + makeToolCall("call_wrapped", "read_wrapped"), + makeToolCall("call_malformed", "read_notes"), + makeToolCall("call_larger", "read_config"), + ], + }, + { + role: "tool", + tool_call_id: "call_uniform", + content: UNIFORM.rawContent, + }, + { + role: "tool", + tool_call_id: "call_wrapped", + content: WRAPPED.rawContent, + }, + { + role: "tool", + tool_call_id: "call_malformed", + content: MALFORMED.rawContent, + }, + { + role: "tool", + tool_call_id: "call_larger", + content: LARGER.rawContent, + }, + ], + }); + + const adapter = minimaxAdapterFactory.createRequestAdapter(makeRequest()); + const stats = await adapter.applyToonCompression("MiniMax-M2"); + + // UNCONDITIONAL apply: every parseable result is replaced — including + // the one whose TOON encoding is larger. Only malformed content is kept. + const expectedMessages = makeRequest().messages; + expectedMessages[3] = { + role: "tool", + tool_call_id: "call_uniform", + content: UNIFORM.expected.encoded as string, + }; + expectedMessages[4] = { + role: "tool", + tool_call_id: "call_wrapped", + content: WRAPPED.expected.encoded as string, + }; + expectedMessages[6] = { + role: "tool", + tool_call_id: "call_larger", + content: LARGER.expected.encoded as string, + }; + expect(adapter.getProviderMessages()).toStrictEqual(expectedMessages); + + // Encoded tokens are always recorded, larger or not; malformed content + // is not counted at all. + const tokensBefore = + countTokens(UNIFORM.expected.normalized) + + countTokens(WRAPPED.expected.normalized) + + countTokens(LARGER.expected.normalized); + const tokensAfter = + countTokens(UNIFORM.expected.encoded as string) + + countTokens(WRAPPED.expected.encoded as string) + + countTokens(LARGER.expected.encoded as string); + expect(stats).toStrictEqual({ + tokensBefore, + tokensAfter, + costSavings: Math.max(0, tokensBefore - tokensAfter), + wasEffective: tokensAfter < tokensBefore, + hadToolResults: true, + }); + }); + + test("applies native results to the right candidates when non-string tool messages are interleaved", async () => { + await upsertOneDollarPerTokenPricing(); + + // Non-string (array-content) tool messages are NOT candidates for the + // native batch: the native result index diverges from both the message + // index and the tool-message index — off-by-one positional application + // would compress the wrong message and fail the equality below. + const makeRequest = (): MinimaxRequest => ({ + model: "MiniMax-M2", + messages: [ + { role: "user", content: "Inspect the workspace." }, + { + role: "assistant", + content: null, + tool_calls: [ + makeToolCall("call_a", "read_notes"), + makeToolCall("call_block", "read_chunks"), + makeToolCall("call_b", "list_files"), + ], + }, + { + role: "tool", + tool_call_id: "call_a", + content: MALFORMED.rawContent, + }, + { + role: "tool", + tool_call_id: "call_block", + content: [{ type: "text", text: "chunk one" }], + }, + { + role: "tool", + tool_call_id: "call_b", + content: UNIFORM.rawContent, + }, + ], + }); + + const adapter = minimaxAdapterFactory.createRequestAdapter(makeRequest()); + const stats = await adapter.applyToonCompression("MiniMax-M2"); + + // Only B compresses; A is malformed and the array-content message is + // untouched. + const expectedMessages = makeRequest().messages; + expectedMessages[4] = { + role: "tool", + tool_call_id: "call_b", + content: UNIFORM.expected.encoded as string, + }; + expect(adapter.getProviderMessages()).toStrictEqual(expectedMessages); + + const tokensBefore = countTokens(UNIFORM.expected.normalized); + const tokensAfter = countTokens(UNIFORM.expected.encoded as string); + expect(stats).toStrictEqual({ + tokensBefore, + tokensAfter, + costSavings: tokensBefore - tokensAfter, + wasEffective: true, + hadToolResults: true, + }); + }); + + describe("exact ToolCompressionStats accounting matrix", () => { + const rows: { + row: string; + entry: GoldenEntry; + compressed: boolean; + counted: boolean; + }[] = [ + { row: "malformed", entry: MALFORMED, compressed: false, counted: false }, + { + row: "unconditional apply (encoded larger, still applied and recorded)", + entry: LARGER, + compressed: true, + counted: true, + }, + { row: "effective", entry: UNIFORM, compressed: true, counted: true }, + { + row: "wrapped-array", + entry: WRAPPED, + compressed: true, + counted: true, + }, + ]; + + for (const { row, entry, compressed, counted } of rows) { + test(row, async () => { + await upsertOneDollarPerTokenPricing(); + + const adapter = minimaxAdapterFactory.createRequestAdapter({ + model: "MiniMax-M2", + messages: [ + { role: "user", content: "run the tool" }, + { role: "tool", tool_call_id: "call_1", content: entry.rawContent }, + ], + }); + const stats = await adapter.applyToonCompression("MiniMax-M2"); + + const tokensBefore = counted + ? countTokens(entry.expected.normalized) + : 0; + const tokensAfter = compressed + ? countTokens(entry.expected.encoded as string) + : tokensBefore; + expect(stats).toStrictEqual({ + tokensBefore, + tokensAfter, + costSavings: Math.max(0, tokensBefore - tokensAfter), + wasEffective: tokensAfter < tokensBefore, + hadToolResults: counted, + }); + + const [, toolMessage] = adapter.getProviderMessages(); + expect(toolMessage).toStrictEqual({ + role: "tool", + tool_call_id: "call_1", + content: compressed + ? (entry.expected.encoded as string) + : entry.rawContent, + }); + }); + } + }); +}); diff --git a/platform/backend/src/routes/proxy/adapters/minimax.ts b/platform/backend/src/routes/proxy/adapters/minimax.ts index 321584fca69..5daa6a23435 100644 --- a/platform/backend/src/routes/proxy/adapters/minimax.ts +++ b/platform/backend/src/routes/proxy/adapters/minimax.ts @@ -1,5 +1,4 @@ import { ArchestraInternalErrorCode } from "@archestra/shared"; -import { encode as toonEncode } from "@toon-format/toon"; import { get } from "lodash-es"; import config from "@/config"; import logger from "@/logging"; @@ -23,7 +22,7 @@ import type { import { extractCommonMessageText } from "@/types"; import type { Minimax } from "@/types/llm-providers"; import type { ToolCompressionStats } from "../utils/toon-conversion"; -import { unwrapToolContent } from "../utils/unwrap-tool-content"; +import { toonEncodeToolResults } from "../utils/toon-native"; // ============================================================================= // TYPE ALIASES @@ -905,7 +904,16 @@ async function convertToolResultsToToon( let totalTokensBefore = 0; let totalTokensAfter = 0; - const result = messages.map((message) => { + // Collect candidate tool messages first so the native unwrap→parse→encode + // transform runs once per request (batched, off the JS thread); results are + // positional and reapplied by message index. + type MinimaxToolMessage = Extract; + const candidates: { + index: number; + message: MinimaxToolMessage; + content: string; + }[] = []; + messages.forEach((message, index) => { if (message.role === "tool") { logger.info( { @@ -917,48 +925,79 @@ async function convertToolResultsToToon( ); if (typeof message.content === "string") { - try { - const unwrapped = unwrapToolContent(message.content); - const parsed = JSON.parse(unwrapped); - const noncompressed = unwrapped; - const compressed = toonEncode(parsed); - - const tokensBefore = tokenizer.countTokens([ - { role: "user", content: noncompressed }, - ]); - const tokensAfter = tokenizer.countTokens([ - { role: "user", content: compressed }, - ]); - - totalTokensBefore += tokensBefore; - totalTokensAfter += tokensAfter; - toolResultCount++; - - logger.info( - { - toolCallId: message.tool_call_id, - tokensBefore, - tokensAfter, - tokensSaved: tokensBefore - tokensAfter, - provider: "minimax", - }, - "convertToolResultsToToon: tool result compressed", - ); - - return { - ...message, - content: compressed, - }; - } catch (err) { - logger.warn( - { err, toolCallId: message.tool_call_id }, - "Failed to compress tool result", - ); - return message; - } + candidates.push({ index, message, content: message.content }); } } - return message; + }); + + const encodedResults = + candidates.length > 0 + ? await toonEncodeToolResults( + candidates.map((candidate) => ({ + id: candidate.message.tool_call_id, + rawContent: candidate.content, + unwrap: true, + })), + ) + : []; + + if (encodedResults === null) { + // Native addon unavailable: fail open — keep every message uncompressed + // and surface the explicit skip reason instead of fabricating stats. + return { + messages, + stats: { + tokensBefore: 0, + tokensAfter: 0, + costSavings: 0, + wasEffective: false, + hadToolResults: candidates.length > 0, + skipReason: "addon_unavailable", + }, + }; + } + + const result = [...messages]; + candidates.forEach((candidate, candidateIndex) => { + const { normalized, encoded: compressed } = encodedResults[candidateIndex]; + const { message } = candidate; + + if (compressed === null) { + logger.warn( + { toolCallId: message.tool_call_id }, + "Failed to compress tool result", + ); + return; + } + + // Token accounting on the normalized (unwrapped) string, exactly as before. + const tokensBefore = tokenizer.countTokens([ + { role: "user", content: normalized }, + ]); + const tokensAfter = tokenizer.countTokens([ + { role: "user", content: compressed }, + ]); + + totalTokensBefore += tokensBefore; + totalTokensAfter += tokensAfter; + toolResultCount++; + + logger.info( + { + toolCallId: message.tool_call_id, + tokensBefore, + tokensAfter, + tokensSaved: tokensBefore - tokensAfter, + provider: "minimax", + }, + "convertToolResultsToToon: tool result compressed", + ); + + // Unconditional apply: MiniMax always records the encoded output. + result[candidate.index] = { + ...message, + content: compressed, + }; }); logger.info( diff --git a/platform/backend/src/routes/proxy/adapters/toon-compression-addon-unavailable.test.ts b/platform/backend/src/routes/proxy/adapters/toon-compression-addon-unavailable.test.ts new file mode 100644 index 00000000000..1b1c2d761c4 --- /dev/null +++ b/platform/backend/src/routes/proxy/adapters/toon-compression-addon-unavailable.test.ts @@ -0,0 +1,226 @@ +// Pins the addon-unavailable contract at the adapter level for every adapter +// cut over to the native TOON kernel: when the helper fails open (resolves to +// null), the adapter leaves the messages untouched — the same object, not +// just structurally equal — and reports the explicit `addon_unavailable` skip +// reason with zeroed totals, never stats fabricated from a transform that did +// not run. Each case also pins the batching contract: the helper is called +// EXACTLY once per request with the full candidate batch. (The OpenAI +// adapter's handler-level counterpart lives in +// routes/toon-addon-unavailable.test.ts.) + +import { vi } from "vitest"; +import { beforeEach, describe, expect, test } from "@/test"; +import type { ToolCompressionStats } from "@/types"; +import { toonEncodeToolResults } from "../utils/toon-native"; +import { anthropicAdapterFactory } from "./anthropic"; +import { bedrockAdapterFactory } from "./bedrock"; +import { cohereAdapterFactory } from "./cohere"; +import { geminiAdapterFactory } from "./gemini"; +import { minimaxAdapterFactory } from "./minimax"; +import { zhipuaiAdapterFactory } from "./zhipuai"; + +// The native addon is unavailable: the helper fails open by resolving to null. +vi.mock("@/routes/proxy/utils/toon-native", () => ({ + toonEncodeToolResults: vi.fn(), + initToonNative: vi.fn(), +})); + +const TOOL_RESULT_JSON_A = JSON.stringify({ + files: [{ name: "README.md" }, { name: "src" }], +}); +const TOOL_RESULT_JSON_B = JSON.stringify({ config: { debug: true } }); + +const EXPECTED_STATS: ToolCompressionStats = { + tokensBefore: 0, + tokensAfter: 0, + costSavings: 0, + wasEffective: false, + hadToolResults: true, + skipReason: "addon_unavailable", +}; + +function expectSingleBatchCall( + expectedItems: { id: string; rawContent: string; unwrap: boolean }[], +) { + expect(vi.mocked(toonEncodeToolResults)).toHaveBeenCalledTimes(1); + expect(vi.mocked(toonEncodeToolResults)).toHaveBeenCalledWith(expectedItems); +} + +describe("adapters with the TOON addon unavailable", () => { + beforeEach(() => { + vi.mocked(toonEncodeToolResults).mockReset().mockResolvedValue(null); + }); + + test("anthropic: one batched call, same messages object, addon_unavailable stats", async () => { + const messages = [ + { + role: "user" as const, + content: [ + { + type: "tool_result" as const, + tool_use_id: "toolu_1", + content: TOOL_RESULT_JSON_A, + }, + { + type: "tool_result" as const, + tool_use_id: "toolu_2", + content: [{ type: "text" as const, text: TOOL_RESULT_JSON_B }], + }, + ], + }, + ]; + const adapter = anthropicAdapterFactory.createRequestAdapter({ + model: "claude-sonnet-4-5", + max_tokens: 1024, + messages, + }); + const stats = await adapter.applyToonCompression("claude-sonnet-4-5"); + + expectSingleBatchCall([ + { id: "toolu_1", rawContent: TOOL_RESULT_JSON_A, unwrap: true }, + { id: "toolu_2", rawContent: TOOL_RESULT_JSON_B, unwrap: true }, + ]); + expect(stats).toStrictEqual(EXPECTED_STATS); + expect(adapter.getProviderMessages()).toBe(messages); + }); + + test("gemini: one batched call, same contents object, addon_unavailable stats", async () => { + const responseA = JSON.parse(TOOL_RESULT_JSON_A); + const responseB = JSON.parse(TOOL_RESULT_JSON_B); + const contents = [ + { + role: "user", + parts: [ + { functionResponse: { name: "list_files", response: responseA } }, + { functionResponse: { name: "read_config", response: responseB } }, + ], + }, + ]; + const adapter = geminiAdapterFactory.createRequestAdapter({ contents }); + const stats = await adapter.applyToonCompression("gemini-2.0-flash"); + + expectSingleBatchCall([ + { id: "list_files", rawContent: TOOL_RESULT_JSON_A, unwrap: true }, + { id: "read_config", rawContent: TOOL_RESULT_JSON_B, unwrap: true }, + ]); + expect(stats).toStrictEqual(EXPECTED_STATS); + expect(adapter.getProviderMessages()).toBe(contents); + }); + + test("bedrock: one batched call, same messages object, addon_unavailable stats", async () => { + const messages = [ + { + role: "user" as const, + content: [ + { + toolResult: { + toolUseId: "tooluse_1", + content: [{ text: TOOL_RESULT_JSON_A }], + }, + }, + { + toolResult: { + toolUseId: "tooluse_2", + content: [{ json: JSON.parse(TOOL_RESULT_JSON_B) }], + }, + }, + ], + }, + ]; + const adapter = bedrockAdapterFactory.createRequestAdapter({ + modelId: "anthropic.claude-sonnet-4-5-20250929-v1:0", + messages, + }); + const stats = await adapter.applyToonCompression( + "anthropic.claude-sonnet-4-5-20250929-v1:0", + ); + + expectSingleBatchCall([ + { id: "tooluse_1", rawContent: TOOL_RESULT_JSON_A, unwrap: false }, + { id: "tooluse_2", rawContent: TOOL_RESULT_JSON_B, unwrap: false }, + ]); + expect(stats).toStrictEqual(EXPECTED_STATS); + expect(adapter.getProviderMessages()).toBe(messages); + }); + + test("zhipuai: one batched call, same messages object, addon_unavailable stats", async () => { + const messages = [ + { + role: "tool" as const, + tool_call_id: "call_1", + content: TOOL_RESULT_JSON_A, + }, + { + role: "tool" as const, + tool_call_id: "call_2", + content: TOOL_RESULT_JSON_B, + }, + ]; + const adapter = zhipuaiAdapterFactory.createRequestAdapter({ + model: "glm-4.6", + messages, + }); + const stats = await adapter.applyToonCompression("glm-4.6"); + + expectSingleBatchCall([ + { id: "call_1", rawContent: TOOL_RESULT_JSON_A, unwrap: true }, + { id: "call_2", rawContent: TOOL_RESULT_JSON_B, unwrap: true }, + ]); + expect(stats).toStrictEqual(EXPECTED_STATS); + expect(adapter.getProviderMessages()).toBe(messages); + }); + + test("minimax: one batched call, same messages object, addon_unavailable stats", async () => { + const messages = [ + { + role: "tool" as const, + tool_call_id: "call_1", + content: TOOL_RESULT_JSON_A, + }, + { + role: "tool" as const, + tool_call_id: "call_2", + content: TOOL_RESULT_JSON_B, + }, + ]; + const adapter = minimaxAdapterFactory.createRequestAdapter({ + model: "MiniMax-M2", + messages, + }); + const stats = await adapter.applyToonCompression("MiniMax-M2"); + + expectSingleBatchCall([ + { id: "call_1", rawContent: TOOL_RESULT_JSON_A, unwrap: true }, + { id: "call_2", rawContent: TOOL_RESULT_JSON_B, unwrap: true }, + ]); + expect(stats).toStrictEqual(EXPECTED_STATS); + expect(adapter.getProviderMessages()).toBe(messages); + }); + + test("cohere: one batched call, same messages object, addon_unavailable stats", async () => { + const messages = [ + { + role: "tool" as const, + tool_call_id: "call_1", + content: TOOL_RESULT_JSON_A, + }, + { + role: "tool" as const, + tool_call_id: "call_2", + content: TOOL_RESULT_JSON_B, + }, + ]; + const adapter = cohereAdapterFactory.createRequestAdapter({ + model: "command-r-plus", + messages, + }); + const stats = await adapter.applyToonCompression("command-r-plus"); + + expectSingleBatchCall([ + { id: "call_1", rawContent: TOOL_RESULT_JSON_A, unwrap: true }, + { id: "call_2", rawContent: TOOL_RESULT_JSON_B, unwrap: true }, + ]); + expect(stats).toStrictEqual(EXPECTED_STATS); + expect(adapter.getProviderMessages()).toBe(messages); + }); +}); diff --git a/platform/backend/src/routes/proxy/adapters/zhipuai-toon-compression.test.ts b/platform/backend/src/routes/proxy/adapters/zhipuai-toon-compression.test.ts new file mode 100644 index 00000000000..8221893824b --- /dev/null +++ b/platform/backend/src/routes/proxy/adapters/zhipuai-toon-compression.test.ts @@ -0,0 +1,298 @@ +// Pins the ZhipuAI adapter's TOON compression cutover to the native addon: +// full transformed-request exact equality (TOON content from the committed v3 +// golden corpus, everything else byte-equal) and the exact +// ToolCompressionStats accounting matrix (rejected payloads counted in BOTH +// totals — the OpenAI-family rule). Requires the built addon: mandatory in +// CI; locally it skips visibly — run `pnpm test:native` from platform/backend. + +import { readFileSync } from "node:fs"; +import path from "node:path"; +import { ModelModel } from "@/models"; +import { describe, expect, test } from "@/test"; +import { getTokenizer } from "@/tokenizers"; +import type { Zhipuai } from "@/types"; +import { zhipuaiAdapterFactory } from "./zhipuai"; + +type ZhipuaiRequest = Zhipuai.Types.ChatCompletionsRequest; + +type GoldenEntry = { + name: string; + rawContent: string; + unwrap: boolean; + expected: { normalized: string; encoded: string | null }; +}; + +const CORPUS_PATH = path.resolve( + import.meta.dirname, + "../../../../../archestra-rs/proxy-transform-core/tests/fixtures/golden-corpus.json", +); +const corpus: GoldenEntry[] = JSON.parse(readFileSync(CORPUS_PATH, "utf8")); +function corpusEntry(name: string): GoldenEntry { + const entry = corpus.find((candidate) => candidate.name === name); + if (!entry) throw new Error(`golden corpus entry not found: ${name}`); + return entry; +} + +// Corpus picks: a uniform array (compression wins), a wrapped +// [{type:"text",...}] payload (unwrapped, compression wins), malformed JSON +// (kept as-is), and a near-boundary object whose TOON encoding does not save +// tokens under the ZhipuAI tokenizer (rejected). +const UNIFORM = corpusEntry("provider-matrix-tool-result"); +const WRAPPED = corpusEntry("wrapped-single-text"); +const MALFORMED = corpusEntry("malformed-prose"); +const NEAR_BOUNDARY = corpusEntry("boundary-obj-1"); + +const tokenizer = getTokenizer("zhipuai"); +const countTokens = (content: string) => + tokenizer.countTokens([{ role: "user", content }]); + +// $1,000,000 per million input tokens = $1 per token, so expected costSavings +// equals tokens saved exactly. +async function upsertOneDollarPerTokenPricing() { + await ModelModel.upsert({ + externalId: "zhipuai/glm-4.6", + provider: "zhipuai", + modelId: "glm-4.6", + inputModalities: null, + outputModalities: null, + customPricePerMillionInput: "1000000.00", + customPricePerMillionOutput: "1000000.00", + lastSyncedAt: new Date(), + }); +} + +const addonLoadError: unknown = await import( + "@archestra/proxy-transform-rs" +).then( + () => null, + (error) => error, +); + +// In CI the suite must FAIL (never skip) when the addon is missing. +const describeNative = + addonLoadError === null || process.env.CI ? describe : describe.skip; +if (addonLoadError !== null && !process.env.CI) { + console.warn( + `[zhipuai-toon-compression.test] skipping: @archestra/proxy-transform-rs is not built (${String( + addonLoadError, + )}). Run \`pnpm test:native\` from platform/backend.`, + ); +} + +function makeToolCall(id: string, name: string) { + return { + id, + type: "function" as const, + function: { name, arguments: '{"directory":"."}' }, + }; +} + +describeNative("ZhipuAI adapter TOON compression (native addon)", () => { + test("transforms the full provider request exactly (goldens for TOON, byte-equal elsewhere)", async () => { + await upsertOneDollarPerTokenPricing(); + + const makeRequest = (): ZhipuaiRequest => ({ + model: "glm-4.6", + temperature: 0.25, + messages: [ + { role: "system", content: "You are a filesystem assistant." }, + { role: "user", content: "What files are in the current directory?" }, + { + role: "assistant", + tool_calls: [ + makeToolCall("call_uniform", "list_files"), + makeToolCall("call_wrapped", "read_wrapped"), + makeToolCall("call_malformed", "read_notes"), + makeToolCall("call_boundary", "read_config"), + ], + }, + { + role: "tool", + tool_call_id: "call_uniform", + content: UNIFORM.rawContent, + }, + { + role: "tool", + tool_call_id: "call_wrapped", + content: WRAPPED.rawContent, + }, + { + role: "tool", + tool_call_id: "call_malformed", + content: MALFORMED.rawContent, + }, + { + role: "tool", + tool_call_id: "call_boundary", + content: NEAR_BOUNDARY.rawContent, + }, + ], + }); + + const adapter = zhipuaiAdapterFactory.createRequestAdapter(makeRequest()); + const stats = await adapter.applyToonCompression("glm-4.6"); + + const expectedRequest = makeRequest(); + // Compression wins for the uniform array and the wrapped payload; the + // malformed and near-boundary results keep their original content. + expectedRequest.messages[3] = { + role: "tool", + tool_call_id: "call_uniform", + content: UNIFORM.expected.encoded as string, + }; + expectedRequest.messages[4] = { + role: "tool", + tool_call_id: "call_wrapped", + content: WRAPPED.expected.encoded as string, + }; + expect(adapter.toProviderRequest()).toStrictEqual(expectedRequest); + + // Rejected payloads count their original tokens in BOTH totals; malformed + // content is not counted at all. + const boundaryTokens = countTokens(NEAR_BOUNDARY.expected.normalized); + const tokensBefore = + countTokens(UNIFORM.expected.normalized) + + countTokens(WRAPPED.expected.normalized) + + boundaryTokens; + const tokensAfter = + countTokens(UNIFORM.expected.encoded as string) + + countTokens(WRAPPED.expected.encoded as string) + + boundaryTokens; + expect(stats).toStrictEqual({ + tokensBefore, + tokensAfter, + costSavings: tokensBefore - tokensAfter, + wasEffective: true, + hadToolResults: true, + }); + }); + + test("applies native results to the right candidates when non-candidate messages are interleaved", async () => { + await upsertOneDollarPerTokenPricing(); + + // ZhipuAI tool messages are string-only, so non-candidates here are + // assistant/user messages between tool messages: the native result index + // diverges from the message index — off-by-one positional application + // would compress the wrong message and fail the equality below. + const makeRequest = (): ZhipuaiRequest => ({ + model: "glm-4.6", + messages: [ + { role: "user", content: "Inspect the workspace." }, + { + role: "assistant", + tool_calls: [makeToolCall("call_a", "read_config")], + }, + { + role: "tool", + tool_call_id: "call_a", + content: NEAR_BOUNDARY.rawContent, + }, + { role: "assistant", content: "Let me look further." }, + { + role: "assistant", + tool_calls: [ + makeToolCall("call_b", "list_files"), + makeToolCall("call_c", "read_notes"), + ], + }, + { + role: "tool", + tool_call_id: "call_b", + content: UNIFORM.rawContent, + }, + { + role: "tool", + tool_call_id: "call_c", + content: MALFORMED.rawContent, + }, + ], + }); + + const adapter = zhipuaiAdapterFactory.createRequestAdapter(makeRequest()); + const stats = await adapter.applyToonCompression("glm-4.6"); + + // Only B compresses; A is rejected (near-boundary), C is malformed. + const expectedRequest = makeRequest(); + expectedRequest.messages[5] = { + role: "tool", + tool_call_id: "call_b", + content: UNIFORM.expected.encoded as string, + }; + expect(adapter.toProviderRequest()).toStrictEqual(expectedRequest); + + const boundaryTokens = countTokens(NEAR_BOUNDARY.expected.normalized); + const tokensBefore = + boundaryTokens + countTokens(UNIFORM.expected.normalized); + const tokensAfter = + boundaryTokens + countTokens(UNIFORM.expected.encoded as string); + expect(stats).toStrictEqual({ + tokensBefore, + tokensAfter, + costSavings: tokensBefore - tokensAfter, + wasEffective: true, + hadToolResults: true, + }); + }); + + describe("exact ToolCompressionStats accounting matrix", () => { + const rows: { + row: string; + entry: GoldenEntry; + compressed: boolean; + counted: boolean; + }[] = [ + { row: "malformed", entry: MALFORMED, compressed: false, counted: false }, + { + row: "ineffective (rejected: original counted in both totals)", + entry: NEAR_BOUNDARY, + compressed: false, + counted: true, + }, + { row: "effective", entry: UNIFORM, compressed: true, counted: true }, + { + row: "wrapped-array", + entry: WRAPPED, + compressed: true, + counted: true, + }, + ]; + + for (const { row, entry, compressed, counted } of rows) { + test(row, async () => { + await upsertOneDollarPerTokenPricing(); + + const adapter = zhipuaiAdapterFactory.createRequestAdapter({ + model: "glm-4.6", + messages: [ + { role: "user", content: "run the tool" }, + { role: "tool", tool_call_id: "call_1", content: entry.rawContent }, + ], + }); + const stats = await adapter.applyToonCompression("glm-4.6"); + + const tokensBefore = counted + ? countTokens(entry.expected.normalized) + : 0; + const tokensAfter = compressed + ? countTokens(entry.expected.encoded as string) + : tokensBefore; + expect(stats).toStrictEqual({ + tokensBefore, + tokensAfter, + costSavings: tokensBefore - tokensAfter, + wasEffective: compressed, + hadToolResults: counted, + }); + + const [, toolMessage] = adapter.getProviderMessages(); + expect(toolMessage).toStrictEqual({ + role: "tool", + tool_call_id: "call_1", + content: compressed + ? (entry.expected.encoded as string) + : entry.rawContent, + }); + }); + } + }); +}); diff --git a/platform/backend/src/routes/proxy/adapters/zhipuai.ts b/platform/backend/src/routes/proxy/adapters/zhipuai.ts index c930c1640f5..93363328b3e 100644 --- a/platform/backend/src/routes/proxy/adapters/zhipuai.ts +++ b/platform/backend/src/routes/proxy/adapters/zhipuai.ts @@ -2,7 +2,6 @@ import { ArchestraInternalErrorCode, ZhipuaiErrorTypes, } from "@archestra/shared"; -import { encode as toonEncode } from "@toon-format/toon"; import { get } from "lodash-es"; import config from "@/config"; import logger from "@/logging"; @@ -26,7 +25,7 @@ import type { Zhipuai, } from "@/types"; import { extractCommonMessageText } from "@/types"; -import { unwrapToolContent } from "../utils/unwrap-tool-content"; +import { toonEncodeToolResults } from "../utils/toon-native"; // ============================================================================= // TYPE ALIASES @@ -831,7 +830,16 @@ async function convertToolResultsToToon( let totalTokensBefore = 0; let totalTokensAfter = 0; - const result = messages.map((message) => { + // Collect candidate tool messages first so the native unwrap→parse→encode + // transform runs once per request (batched, off the JS thread); results are + // positional and reapplied by message index. + type ZhipuaiToolMessage = Extract; + const candidates: { + index: number; + message: ZhipuaiToolMessage; + content: string; + }[] = []; + messages.forEach((message, index) => { if (message.role === "tool") { logger.info( { @@ -843,85 +851,111 @@ async function convertToolResultsToToon( ); if (typeof message.content === "string") { - try { - const unwrapped = unwrapToolContent(message.content); - const parsed = JSON.parse(unwrapped); - const noncompressed = unwrapped; - const compressed = toonEncode(parsed); - - const tokensBefore = tokenizer.countTokens([ - { role: "user", content: noncompressed }, - ]); - const tokensAfter = tokenizer.countTokens([ - { role: "user", content: compressed }, - ]); - - toolResultCount++; - - // Always count tokens before - totalTokensBefore += tokensBefore; - - // Only apply compression if it actually saves tokens - if (tokensAfter < tokensBefore) { - totalTokensAfter += tokensAfter; - - logger.info( - { - toolCallId: message.tool_call_id, - beforeLength: noncompressed.length, - afterLength: compressed.length, - tokensBefore, - tokensAfter, - toonPreview: compressed.substring(0, 150), - provider: "zhipuai", - }, - "convertToolResultsToToon: compressed", - ); - logger.trace( - { - toolCallId: message.tool_call_id, - before: noncompressed, - after: compressed, - provider: "zhipuai", - supposedToBeJson: parsed, - }, - "convertToolResultsToToon: before/after", - ); + candidates.push({ index, message, content: message.content }); + } + } + }); - return { - ...message, - content: compressed, - }; - } + const encodedResults = + candidates.length > 0 + ? await toonEncodeToolResults( + candidates.map((candidate) => ({ + id: candidate.message.tool_call_id, + rawContent: candidate.content, + unwrap: true, + })), + ) + : []; + + if (encodedResults === null) { + // Native addon unavailable: fail open — keep every message uncompressed + // and surface the explicit skip reason instead of fabricating stats. + return { + messages, + stats: { + tokensBefore: 0, + tokensAfter: 0, + costSavings: 0, + wasEffective: false, + hadToolResults: candidates.length > 0, + skipReason: "addon_unavailable", + }, + }; + } - // Compression not applied - count non-compressed tokens to track total tokens anyway - totalTokensAfter += tokensBefore; - logger.info( - { - toolCallId: message.tool_call_id, - tokensBefore, - tokensAfter, - provider: "zhipuai", - }, - "Skipping TOON compression - compressed output has more tokens", - ); - } catch { - logger.info( - { - toolCallId: message.tool_call_id, - contentPreview: - typeof message.content === "string" - ? message.content.substring(0, 100) - : "non-string", - }, - "Skipping TOON conversion - content is not JSON", - ); - return message; - } - } + const result = [...messages]; + candidates.forEach((candidate, candidateIndex) => { + const { normalized, encoded: compressed } = encodedResults[candidateIndex]; + const { message } = candidate; + + if (compressed === null) { + logger.info( + { + toolCallId: message.tool_call_id, + contentPreview: candidate.content.substring(0, 100), + }, + "Skipping TOON conversion - content is not JSON", + ); + return; + } + + // Token accounting on the normalized (unwrapped) string, exactly as before. + const tokensBefore = tokenizer.countTokens([ + { role: "user", content: normalized }, + ]); + const tokensAfter = tokenizer.countTokens([ + { role: "user", content: compressed }, + ]); + + toolResultCount++; + + // Always count tokens before + totalTokensBefore += tokensBefore; + + // Only apply compression if it actually saves tokens + if (tokensAfter < tokensBefore) { + totalTokensAfter += tokensAfter; + + logger.info( + { + toolCallId: message.tool_call_id, + beforeLength: normalized.length, + afterLength: compressed.length, + tokensBefore, + tokensAfter, + toonPreview: compressed.substring(0, 150), + provider: "zhipuai", + }, + "convertToolResultsToToon: compressed", + ); + logger.trace( + { + toolCallId: message.tool_call_id, + before: normalized, + after: compressed, + provider: "zhipuai", + }, + "convertToolResultsToToon: before/after", + ); + + result[candidate.index] = { + ...message, + content: compressed, + }; + return; } - return message; + // Compression not applied - count non-compressed tokens to track total tokens anyway + totalTokensAfter += tokensBefore; + logger.info( + { + toolCallId: message.tool_call_id, + tokensBefore, + tokensAfter, + provider: "zhipuai", + }, + "Skipping TOON compression - compressed output has more tokens", + ); }); logger.info( From 93f91ae4e847909c4a7fbbdaf865d77d56eed6c4 Mon Sep 17 00:00:00 2001 From: Arseny Kravchenko Date: Fri, 10 Jul 2026 15:18:37 +0200 Subject: [PATCH 07/18] bench(proxy): dual-backend TOON kernel benchmarks + criterion diagnostic BENCH_BACKEND=ts|native selector over the same harness/corpora, a one-off backend output comparator, and a criterion bench in proxy-transform-core. Records the T8 verdict: native FAILS the pre-registered thresholds (1.9x slower at 1KB to 103x at 5MB; peak RSS ~2.4GB vs <705MB cap; only the event-loop-delay guardrail passes). Root cause isolated to the upstream toon-format 0.5.0 encoder: O(k^2) sibling-key scan in write_object_impl plus 36-50MB/s linear paths vs ~80MB/s for the whole TS pipeline; the NAPI boundary itself measured negligible. Claude-Session: https://claude.ai/code/session_01QkeLJprfeq75d2NY39aFiu --- platform/archestra-rs/Cargo.lock | 241 +++++++++++++++++- .../proxy-transform-core/Cargo.toml | 5 + .../benches/toon_kernel.rs | 168 ++++++++++++ .../proxy/__bench__/bench-concurrency.ts | 22 +- .../proxy/__bench__/bench-toon-kernel.ts | 44 ++-- .../proxy/__bench__/compare-backends.ts | 154 +++++++++++ .../routes/proxy/__bench__/toon-backend.ts | 61 +++++ 7 files changed, 669 insertions(+), 26 deletions(-) create mode 100644 platform/archestra-rs/proxy-transform-core/benches/toon_kernel.rs create mode 100644 platform/backend/src/routes/proxy/__bench__/compare-backends.ts create mode 100644 platform/backend/src/routes/proxy/__bench__/toon-backend.ts diff --git a/platform/archestra-rs/Cargo.lock b/platform/archestra-rs/Cargo.lock index adb76d96c33..15a29129a95 100644 --- a/platform/archestra-rs/Cargo.lock +++ b/platform/archestra-rs/Cargo.lock @@ -17,6 +17,18 @@ dependencies = [ "memchr", ] +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + [[package]] name = "anyhow" version = "1.0.103" @@ -161,6 +173,12 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + [[package]] name = "cc" version = "1.2.65" @@ -191,6 +209,58 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "clap" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +dependencies = [ + "anstyle", + "clap_lex", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + [[package]] name = "cmake" version = "0.1.58" @@ -269,6 +339,73 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "criterion" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "is-terminal", + "itertools 0.10.5", + "num-traits", + "once_cell", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools 0.10.5", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + [[package]] name = "crypto-common" version = "0.1.7" @@ -756,6 +893,17 @@ dependencies = [ "tracing", ] +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + [[package]] name = "hashbrown" version = "0.17.1" @@ -768,6 +916,12 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + [[package]] name = "hex" version = "0.4.3" @@ -1120,6 +1274,26 @@ version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" +[[package]] +name = "is-terminal" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.14.0" @@ -1404,6 +1578,12 @@ version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + [[package]] name = "openssl-probe" version = "0.2.1" @@ -1551,6 +1731,34 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "plotters" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" + +[[package]] +name = "plotters-svg" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +dependencies = [ + "plotters-backend", +] + [[package]] name = "png" version = "0.18.1" @@ -1633,7 +1841,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf" dependencies = [ "anyhow", - "itertools", + "itertools 0.14.0", "proc-macro2", "quote", "syn 2.0.118", @@ -1643,6 +1851,7 @@ dependencies = [ name = "proxy_transform_core" version = "0.1.0" dependencies = [ + "criterion", "napi", "napi-derive", "proptest", @@ -1795,6 +2004,26 @@ dependencies = [ "rand_core", ] +[[package]] +name = "rayon" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "redox_syscall" version = "0.5.18" @@ -2498,6 +2727,16 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "tinyvec" version = "1.11.0" diff --git a/platform/archestra-rs/proxy-transform-core/Cargo.toml b/platform/archestra-rs/proxy-transform-core/Cargo.toml index a46bb9fb609..8ed33c295be 100644 --- a/platform/archestra-rs/proxy-transform-core/Cargo.toml +++ b/platform/archestra-rs/proxy-transform-core/Cargo.toml @@ -15,6 +15,11 @@ serde_json = { version = "1", features = ["preserve_order"] } toon-format = { version = "0.5.0", default-features = false } [dev-dependencies] +criterion = "0.5" proptest = "1" # fixture (de)serialization in the golden-corpus test serde = { version = "1", features = ["derive"] } + +[[bench]] +name = "toon_kernel" +harness = false diff --git a/platform/archestra-rs/proxy-transform-core/benches/toon_kernel.rs b/platform/archestra-rs/proxy-transform-core/benches/toon_kernel.rs new file mode 100644 index 00000000000..9e85297cb03 --- /dev/null +++ b/platform/archestra-rs/proxy-transform-core/benches/toon_kernel.rs @@ -0,0 +1,168 @@ +//! Diagnostic Criterion bench for the pure TOON kernel (no NAPI boundary). +//! The pre-registered performance threshold is measured by the JS harness +//! (`platform/backend/src/routes/proxy/__bench__/`); this bench isolates the +//! Rust-side cost per payload size on the same synthetic shapes as that +//! harness's corpus builder (uniform SKU rows / nested objects / prose, with +//! text-block wrappers and Bedrock-style `unwrap: false` items mixed in). + +use criterion::{ + BatchSize, BenchmarkId, Criterion, Throughput, black_box, criterion_group, criterion_main, +}; +use proxy_transform_core::{ToonEncodeItem, toon_encode_tool_results}; +use serde_json::{Value, json}; + +/// (label, payload bytes) per corpus; every corpus is a 10-item batch covering +/// one full mix cycle of the JS corpus builder. +const PAYLOAD_SIZES: [(&str, usize); 4] = [ + ("1KB", 1 << 10), + ("10KB", 10 << 10), + ("100KB", 100 << 10), + ("1MB", 1 << 20), +]; +const ITEMS_PER_BATCH: usize = 10; + +fn build_batch(payload_bytes: usize, seed: u64) -> Vec { + let mut rng = TinyRng::new(seed); + (0..ITEMS_PER_BATCH) + .map(|i| { + // Same mix cycle as corpus.ts: per 10 items, 6 uniform arrays, + // 2 non-array objects, 2 non-JSON prose; 2 JSON items wrapped in + // the `[{"type":"text","text":...}]` client wrapper. + let kind = match i % 10 { + 2 | 7 => PayloadKind::Object, + 4 | 9 => PayloadKind::NonJson, + _ => PayloadKind::Array, + }; + let wrapped = !matches!(kind, PayloadKind::NonJson) && matches!(i % 10, 6 | 7); + let mut payload = build_payload(kind, payload_bytes, &mut rng); + if wrapped { + payload = serde_json::to_string(&json!([{ "type": "text", "text": payload }])) + .expect("wrapper serializes"); + } + ToonEncodeItem { + id: format!("bench_{i}"), + raw_content: payload, + unwrap: if wrapped { true } else { i % 7 != 3 }, + } + }) + .collect() +} + +enum PayloadKind { + Array, + Object, + NonJson, +} + +const WORDS: [&str; 12] = [ + "alpha", "bravo", "charlie", "delta", "echo", "foxtrot", "golf", "hotel", "india", "juliet", + "kilo", "lima", +]; +const STATUSES: [&str; 4] = ["active", "pending", "archived", "failed"]; + +fn make_row(rng: &mut TinyRng, id: usize) -> Value { + json!({ + "id": id, + "sku": format!("SKU-{}", rng.below(1_000_000)), + "name": format!("{} {}", WORDS[rng.below(WORDS.len())], WORDS[rng.below(WORDS.len())]), + "status": STATUSES[rng.below(STATUSES.len())], + "score": (rng.below(10_000) as f64) / 100.0, + "quantity": rng.below(500), + "active": rng.below(2) == 0, + "updatedAt": format!("2026-0{}-{:02}T12:00:00Z", 1 + rng.below(6), 1 + rng.below(28)), + }) +} + +fn build_payload(kind: PayloadKind, target_bytes: usize, rng: &mut TinyRng) -> String { + match kind { + PayloadKind::Array => { + let mut rows = Vec::new(); + let mut size = 2; + let mut id = 0; + while size < target_bytes { + let row = make_row(rng, id); + id += 1; + size += serde_json::to_string(&row).expect("row serializes").len() + 1; + rows.push(row); + } + serde_json::to_string(&Value::Array(rows)).expect("array serializes") + } + PayloadKind::Object => { + let mut entries = serde_json::Map::new(); + let mut size = 64; + let mut id = 0; + while size < target_bytes { + let key = format!("entry_{id}"); + let mut value = make_row(rng, id); + value["nested"] = json!({ + "tags": [WORDS[rng.below(WORDS.len())], WORDS[rng.below(WORDS.len())]], + "depth": 2, + }); + size += serde_json::to_string(&value) + .expect("entry serializes") + .len() + + key.len() + + 4; + entries.insert(key, value); + id += 1; + } + serde_json::to_string(&json!({ + "meta": { "source": "bench", "version": 3, "total": id }, + "entries": entries, + })) + .expect("object serializes") + } + PayloadKind::NonJson => { + let mut parts = vec![format!("Tool run {} output:", rng.below(1000))]; + let mut size = parts[0].len(); + while size < target_bytes { + let word = WORDS[rng.below(WORDS.len())]; + size += word.len() + 1; + parts.push(word.to_string()); + } + parts.join(" ") + } + } +} + +fn bench_toon_kernel(c: &mut Criterion) { + let mut group = c.benchmark_group("toon_kernel"); + group.sample_size(20); + for (label, payload_bytes) in PAYLOAD_SIZES { + let batch = build_batch(payload_bytes, 0x5eed ^ payload_bytes as u64); + let total_bytes: usize = batch.iter().map(|item| item.raw_content.len()).sum(); + group.throughput(Throughput::Bytes(total_bytes as u64)); + group.bench_with_input(BenchmarkId::from_parameter(label), &batch, |b, batch| { + b.iter_batched( + || batch.clone(), + |items| black_box(toon_encode_tool_results(black_box(items))), + BatchSize::LargeInput, + ); + }); + } + group.finish(); +} + +#[derive(Clone, Copy)] +struct TinyRng(u64); + +impl TinyRng { + fn new(seed: u64) -> Self { + Self(seed) + } + + fn next(&mut self) -> u64 { + self.0 = self + .0 + .wrapping_mul(6_364_136_223_846_793_005) + .wrapping_add(1); + self.0 + } + + fn below(&mut self, upper: usize) -> usize { + ((self.next() >> 16) as usize) % upper + } +} + +criterion_group!(benches, bench_toon_kernel); +criterion_main!(benches); diff --git a/platform/backend/src/routes/proxy/__bench__/bench-concurrency.ts b/platform/backend/src/routes/proxy/__bench__/bench-concurrency.ts index c43e24df6e3..d8746ba334d 100644 --- a/platform/backend/src/routes/proxy/__bench__/bench-concurrency.ts +++ b/platform/backend/src/routes/proxy/__bench__/bench-concurrency.ts @@ -3,19 +3,19 @@ * * Runs 8 concurrent async batches of the TOON kernel (mixed 1KB-5MB items, * ~8.8MB per batch, ~70MB total) with an event-loop yield between items, - * and reports p50/p99/max event-loop delay plus peak RSS. + * and reports p50/p99/max event-loop delay plus peak RSS. Backend selectable + * via BENCH_BACKEND=ts|native (see toon-backend.ts). * * Run from platform/backend: * pnpm exec tsx src/routes/proxy/__bench__/bench-concurrency.ts + * BENCH_BACKEND=native pnpm exec tsx src/routes/proxy/__bench__/bench-concurrency.ts */ import { monitorEventLoopDelay, performance } from "node:perf_hooks"; import { setImmediate as yieldEventLoop } from "node:timers/promises"; import { fmt } from "./bench-util"; import { batchBytes, buildBatch, type CorpusSpec } from "./corpus"; -import { - encodeToolResultsReference, - type ToonKernelItem, -} from "./toon-kernel-reference"; +import { resolveToonBackend, type ToonBenchBackend } from "./toon-backend"; +import type { ToonKernelItem } from "./toon-kernel-reference"; const CONCURRENCY = 8; @@ -42,9 +42,12 @@ function sampleRss(): void { } } -async function worker(items: ToonKernelItem[]): Promise { +async function worker( + backend: ToonBenchBackend, + items: ToonKernelItem[], +): Promise { for (const item of items) { - const [result] = encodeToolResultsReference([item]); + const [result] = await backend.encode([item]); sink += result.encoded === null ? 0 : result.encoded.length; sampleRss(); await yieldEventLoop(); @@ -52,6 +55,7 @@ async function worker(items: ToonKernelItem[]): Promise { } async function main(): Promise { + const backend = await resolveToonBackend(); const batches: ToonKernelItem[][] = []; for (let i = 0; i < CONCURRENCY; i++) { batches.push(buildWorkerBatch(1000 + i)); @@ -64,7 +68,7 @@ async function main(): Promise { const rssTimer = setInterval(sampleRss, 25); histogram.enable(); const start = performance.now(); - await Promise.all(batches.map((b) => worker(b))); + await Promise.all(batches.map((b) => worker(backend, b))); const wallMs = performance.now() - start; histogram.disable(); clearInterval(rssTimer); @@ -72,7 +76,7 @@ async function main(): Promise { const toMs = (ns: number) => ns / 1e6; console.info( - "bench-concurrency: 8 concurrent TOON kernel batches (baseline)", + `bench-concurrency: 8 concurrent TOON kernel batches (${backend.name} backend)`, ); console.info( [ diff --git a/platform/backend/src/routes/proxy/__bench__/bench-toon-kernel.ts b/platform/backend/src/routes/proxy/__bench__/bench-toon-kernel.ts index e02d771ca7d..dfd7337a433 100644 --- a/platform/backend/src/routes/proxy/__bench__/bench-toon-kernel.ts +++ b/platform/backend/src/routes/proxy/__bench__/bench-toon-kernel.ts @@ -1,9 +1,11 @@ /** - * Benchmark (a): TOON kernel (unwrap -> JSON.parse -> toonEncode) via the TS - * reference backend, over deterministic synthetic corpora. + * Benchmark (a): TOON kernel (unwrap -> JSON.parse -> toonEncode) over + * deterministic synthetic corpora, against a selectable backend + * (BENCH_BACKEND=ts|native — see toon-backend.ts). * * Run from platform/backend: * pnpm exec tsx src/routes/proxy/__bench__/bench-toon-kernel.ts + * BENCH_BACKEND=native pnpm exec tsx src/routes/proxy/__bench__/bench-toon-kernel.ts */ import { performance } from "node:perf_hooks"; import { fmt, summarize } from "./bench-util"; @@ -13,10 +15,8 @@ import { buildJumboBatch, CORPUS_SPECS, } from "./corpus"; -import { - encodeToolResultsReference, - type ToonKernelItem, -} from "./toon-kernel-reference"; +import { resolveToonBackend, type ToonBenchBackend } from "./toon-backend"; +import type { ToonKernelItem } from "./toon-kernel-reference"; const TIME_BUDGET_MS = 4_000; const MIN_ITERATIONS = 5; @@ -25,9 +25,12 @@ const MAX_ITERATIONS = 200; // Prevents dead-code elimination of the encode results. let sink = 0; -function runBatch(items: ToonKernelItem[]): number { +async function runBatch( + backend: ToonBenchBackend, + items: ToonKernelItem[], +): Promise { const start = performance.now(); - const results = encodeToolResultsReference(items); + const results = await backend.encode(items); const elapsed = performance.now() - start; for (const r of results) { sink += r.encoded === null ? r.normalized.length : r.encoded.length; @@ -35,9 +38,13 @@ function runBatch(items: ToonKernelItem[]): number { return elapsed; } -function benchCorpus(name: string, items: ToonKernelItem[]): void { +async function benchCorpus( + backend: ToonBenchBackend, + name: string, + items: ToonKernelItem[], +): Promise { const totalMB = batchBytes(items) / (1 << 20); - runBatch(items); // warmup + await runBatch(backend, items); // warmup const samples: number[] = []; const budgetStart = performance.now(); while ( @@ -45,7 +52,7 @@ function benchCorpus(name: string, items: ToonKernelItem[]): void { (samples.length < MIN_ITERATIONS || performance.now() - budgetStart < TIME_BUDGET_MS) ) { - samples.push(runBatch(items)); + samples.push(await runBatch(backend, items)); } const s = summarize(samples); const mbPerSec = totalMB / (s.meanMs / 1000); @@ -64,9 +71,14 @@ function benchCorpus(name: string, items: ToonKernelItem[]): void { ); } -console.info("bench-toon-kernel: TS reference backend (baseline)"); -for (const spec of CORPUS_SPECS) { - benchCorpus(spec.name, buildBatch(spec, 42)); +async function main(): Promise { + const backend = await resolveToonBackend(); + console.info(`bench-toon-kernel: ${backend.name} backend`); + for (const spec of CORPUS_SPECS) { + await benchCorpus(backend, spec.name, buildBatch(spec, 42)); + } + await benchCorpus(backend, "70MB", buildJumboBatch(4242)); + console.info(`(sink=${sink})`); } -benchCorpus("70MB", buildJumboBatch(4242)); -console.info(`(sink=${sink})`); + +main(); diff --git a/platform/backend/src/routes/proxy/__bench__/compare-backends.ts b/platform/backend/src/routes/proxy/__bench__/compare-backends.ts new file mode 100644 index 00000000000..71fba2364c2 --- /dev/null +++ b/platform/backend/src/routes/proxy/__bench__/compare-backends.ts @@ -0,0 +1,154 @@ +/** + * One-off sanity script (not a permanent test): runs the TS reference backend + * and the native backend over the full benchmark corpora and classifies every + * output divergence. Expected: `normalized` byte-equal everywhere; `encoded` + * either byte-equal or a representation-only difference (both encodings decode + * to the same value via the npm decoder — e.g. the Rust v3 encoder quotes + * hyphenated scalars like `SKU-123` that npm 2.1.0 leaves bare, per the known + * npm→v3 migration delta established at T1). Anything that decodes differently + * is a real mismatch and fails the script. + * + * Run from platform/backend: + * pnpm exec tsx src/routes/proxy/__bench__/compare-backends.ts + */ +import "./bench-env"; +import assert from "node:assert/strict"; +import { decode as toonDecode } from "@toon-format/toon"; +import { buildBatch, buildJumboBatch, CORPUS_SPECS } from "./corpus"; +import { + encodeToolResultsReference, + type ToonKernelItem, +} from "./toon-kernel-reference"; + +const MAX_PRINTED_DIFFS = 10; + +type DivergenceKind = + | "normalized" // normalized strings differ (must never happen) + | "encodability" // one side encoded, the other returned null + | "representation" // byte-different encodings decoding to the same value + | "semantic"; // encodings decode to different values + +interface Divergence { + corpus: string; + index: number; + kind: DivergenceKind; + detail: string; +} + +async function main(): Promise { + const divergences: Divergence[] = []; + let total = 0; + let encodable = 0; + for (const spec of CORPUS_SPECS) { + const r = await compareCorpus(spec.name, buildBatch(spec, 42), divergences); + total += r.total; + encodable += r.encodable; + } + const jumbo = await compareCorpus("70MB", buildJumboBatch(4242), divergences); + total += jumbo.total; + encodable += jumbo.encodable; + + const byKind = new Map(); + for (const d of divergences) { + const bucket = byKind.get(d.kind) ?? []; + bucket.push(d); + byKind.set(d.kind, bucket); + } + console.info( + `compare-backends: ${total} items (${encodable} encodable), ${divergences.length} byte-divergence(s)`, + ); + for (const [kind, bucket] of byKind) { + console.info(` ${kind}: ${bucket.length}`); + for (const d of bucket.slice(0, MAX_PRINTED_DIFFS)) { + console.info(` ${d.corpus}[${d.index}] ${d.detail}`); + } + if (bucket.length > MAX_PRINTED_DIFFS) { + console.info(` ... ${bucket.length - MAX_PRINTED_DIFFS} more`); + } + } + + const broken = divergences.filter((d) => d.kind !== "representation"); + process.exitCode = broken.length === 0 ? 0 : 1; +} + +// ============================================================================= +// INTERNALS +// ============================================================================= + +async function compareCorpus( + name: string, + items: ToonKernelItem[], + divergences: Divergence[], +): Promise<{ total: number; encodable: number }> { + const { toonEncodeToolResults } = await import("../utils/toon-native"); + const tsResults = encodeToolResultsReference(items); + const nativeResults = await toonEncodeToolResults( + items.map(({ rawContent, unwrap }, i) => ({ + id: `cmp_${i}`, + rawContent, + unwrap, + })), + ); + if (nativeResults === null) { + throw new Error("native backend unavailable"); + } + let encodable = 0; + items.forEach((_, i) => { + const ts = tsResults[i]; + const native = nativeResults[i]; + if (ts.normalized !== native.normalized) { + divergences.push({ + corpus: name, + index: i, + kind: "normalized", + detail: firstDiff(ts.normalized, native.normalized), + }); + } + if (ts.encoded !== null || native.encoded !== null) { + encodable++; + } + if (ts.encoded === native.encoded) { + return; + } + if (ts.encoded === null || native.encoded === null) { + divergences.push({ + corpus: name, + index: i, + kind: "encodability", + detail: `ts=${ts.encoded === null ? "" : "encoded"} native=${ + native.encoded === null ? "" : "encoded" + }`, + }); + return; + } + divergences.push({ + corpus: name, + index: i, + kind: classifyEncodedDiff(ts.encoded, native.encoded), + detail: firstDiff(ts.encoded, native.encoded), + }); + }); + return { total: items.length, encodable }; +} + +function classifyEncodedDiff(ts: string, native: string): DivergenceKind { + try { + assert.deepEqual(toonDecode(native), toonDecode(ts)); + return "representation"; + } catch { + return "semantic"; + } +} + +function firstDiff(a: string, b: string): string { + const n = Math.min(a.length, b.length); + let i = 0; + while (i < n && a[i] === b[i]) { + i++; + } + const ctx = (s: string) => + JSON.stringify(s.slice(Math.max(0, i - 40), i + 40)); + return `at byte ${i}: ts=${ctx(a)} native=${ctx(b)}`; +} + +main(); diff --git a/platform/backend/src/routes/proxy/__bench__/toon-backend.ts b/platform/backend/src/routes/proxy/__bench__/toon-backend.ts new file mode 100644 index 00000000000..421d3f386b5 --- /dev/null +++ b/platform/backend/src/routes/proxy/__bench__/toon-backend.ts @@ -0,0 +1,61 @@ +/** + * Benchmark backend selection for the TOON kernel harness (T0/T8): + * BENCH_BACKEND=ts (default) TS reference implementation + * BENCH_BACKEND=native the real production helper (utils/toon-native.ts) + * over the Rust addon, so JS→Rust string copies, async + * scheduling, and result conversion are all inside the + * measurement. + * + * Both backends share the "batch of { rawContent, unwrap } → results" + * boundary, corpora, and stats output — the pre-registered threshold compares + * exactly these two numbers. + */ +import "./bench-env"; +import { + encodeToolResultsReference, + type ToonKernelItem, + type ToonKernelResult, +} from "./toon-kernel-reference"; + +export interface ToonBenchBackend { + name: "ts" | "native"; + encode: (items: ToonKernelItem[]) => Promise; +} + +export async function resolveToonBackend(): Promise { + const requested = process.env.BENCH_BACKEND ?? "ts"; + switch (requested) { + case "ts": + return { + name: "ts", + encode: async (items) => encodeToolResultsReference(items), + }; + case "native": { + // Dynamic import keeps the backend module graph (logging, metrics, + // config) out of TS-backend runs. + const { toonEncodeToolResults } = await import("../utils/toon-native"); + return { + name: "native", + encode: async (items) => { + const results = await toonEncodeToolResults( + items.map(({ rawContent, unwrap }, i) => ({ + id: `bench_${i}`, + rawContent, + unwrap, + })), + ); + if (results === null) { + throw new Error( + "native TOON backend unavailable (addon failed to load)", + ); + } + return results; + }, + }; + } + default: + throw new Error( + `unknown BENCH_BACKEND "${requested}" (expected "ts" or "native")`, + ); + } +} From 4223cca0f2d929f59dc5f919d8188414b7cf41e5 Mon Sep 17 00:00:00 2001 From: Arseny Kravchenko Date: Fri, 10 Jul 2026 17:08:30 +0200 Subject: [PATCH 08/18] perf(archestra-rs): replace toon-format encoder with own linear implementation The upstream crate's encoder failed the pre-registered gate (O(k^2) sibling-key scan, 36-50MB/s linear paths). Own encoder + borrowed JSON DOM (DeserializeSeed over serde_json): byte parity pinned by the 120 goldens and a differential proptest against the crate (now a dev-dependency, oracle only), 314-344 MiB/s, all six bench corpora at 0.26-0.43x of the TypeScript baseline - the >=50% CPU-reduction threshold passes everywhere, p99 event-loop delay 13ms vs 95ms baseline, RSS growth on par with TS. Hardened for untrusted input after two adversarial review rounds: per-item output budget max(2x input, 16KiB) + aggregate batch budget (fail-open to encoded:null - output above 2x bytes can never win the token comparison), 10MiB per-item input cap bounding parser-DOM allocation, exact-size pre-write checks bounding capacity overshoot, O(total fields) reordered-tabular detection, SipHash object index, runtime fail-closed exponent-format check. Replicates crate quirks exactly where reachable (saturating as_i64/as_u64 float casts, [N]{}: arrays, no-exponent floats). Claude-Session: https://claude.ai/code/session_01QkeLJprfeq75d2NY39aFiu --- platform/archestra-rs/Cargo.lock | 1 + .../proxy-transform-core/Cargo.toml | 14 +- .../proxy-transform-core/src/encode.rs | 917 ++++++++++++++++++ .../proxy-transform-core/src/json.rs | 369 +++++++ .../proxy-transform-core/src/lib.rs | 225 ++++- .../roundtrip_property.proptest-regressions | 7 + .../tests/roundtrip_property.rs | 57 +- .../proxy-transform-rs/index.d.ts | 4 +- .../proxy/__bench__/compare-backends.ts | 154 --- .../proxy/__bench__/toon-kernel-reference.ts | 38 - .../proxy/__bench__/validate-reference.ts | 128 --- .../proxy/utils/unwrap-tool-content.test.ts | 82 -- .../routes/proxy/utils/unwrap-tool-content.ts | 40 - 13 files changed, 1562 insertions(+), 474 deletions(-) create mode 100644 platform/archestra-rs/proxy-transform-core/src/encode.rs create mode 100644 platform/archestra-rs/proxy-transform-core/src/json.rs create mode 100644 platform/archestra-rs/proxy-transform-core/tests/roundtrip_property.proptest-regressions delete mode 100644 platform/backend/src/routes/proxy/__bench__/compare-backends.ts delete mode 100644 platform/backend/src/routes/proxy/__bench__/toon-kernel-reference.ts delete mode 100644 platform/backend/src/routes/proxy/__bench__/validate-reference.ts delete mode 100644 platform/backend/src/routes/proxy/utils/unwrap-tool-content.test.ts delete mode 100644 platform/backend/src/routes/proxy/utils/unwrap-tool-content.ts diff --git a/platform/archestra-rs/Cargo.lock b/platform/archestra-rs/Cargo.lock index 15a29129a95..a8c5710d345 100644 --- a/platform/archestra-rs/Cargo.lock +++ b/platform/archestra-rs/Cargo.lock @@ -1852,6 +1852,7 @@ name = "proxy_transform_core" version = "0.1.0" dependencies = [ "criterion", + "itoa", "napi", "napi-derive", "proptest", diff --git a/platform/archestra-rs/proxy-transform-core/Cargo.toml b/platform/archestra-rs/proxy-transform-core/Cargo.toml index 8ed33c295be..3d3a0bce050 100644 --- a/platform/archestra-rs/proxy-transform-core/Cargo.toml +++ b/platform/archestra-rs/proxy-transform-core/Cargo.toml @@ -9,16 +9,26 @@ default = [] napi = ["dep:napi", "dep:napi-derive"] [dependencies] +itoa = "1" napi = { version = "3", optional = true } napi-derive = { version = "3", optional = true } -serde_json = { version = "1", features = ["preserve_order"] } -toon-format = { version = "0.5.0", default-features = false } +# de traits for the borrowed JSON DOM (src/json.rs); derive only in tests +serde = "1" +serde_json = "1" [dev-dependencies] criterion = "0.5" proptest = "1" # fixture (de)serialization in the golden-corpus test serde = { version = "1", features = ["derive"] } +# preserve_order is oracle-only: the production parser builds its own ordered +# JsonObject, but tests compare against toon-format over serde_json::Value, +# which must see document key order. Keeping the feature out of [dependencies] +# drops indexmap from the production (napi) build. +serde_json = { version = "1", features = ["preserve_order"] } +# parity oracle for the encoder (differential + round-trip tests) — the +# production path uses only src/encode.rs +toon-format = { version = "0.5.0", default-features = false } [[bench]] name = "toon_kernel" diff --git a/platform/archestra-rs/proxy-transform-core/src/encode.rs b/platform/archestra-rs/proxy-transform-core/src/encode.rs new file mode 100644 index 00000000000..3c19e5fdadd --- /dev/null +++ b/platform/archestra-rs/proxy-transform-core/src/encode.rs @@ -0,0 +1,917 @@ +//! TOON (spec v3) encoder, byte-compatible with `toon-format` 0.5.0 +//! `encode_default` (2-space indent, comma delimiter, key folding off), but +//! linear-time and low-allocation: a single pass over the borrowed +//! [`JsonValue`] DOM writing into one output buffer. The upstream crate stays +//! as a dev-dependency and is the parity oracle: the committed golden corpus +//! plus the differential property test in `tests/roundtrip_property.rs` pin +//! byte equality. +//! +//! Trust boundary: input values come from [`crate::json::parse_json`], so +//! floats are always finite — the crate's NaN/Infinity→null normalization is +//! unreachable here and not replicated. Its -0.0→0 normalization falls out of +//! the integer-collapse float path below. +//! +//! Deliberate divergence from the crate — the OUTPUT BUDGET: TOON expands +//! exponent-form numbers to full decimal literals (`1e300`, 5 JSON bytes, +//! becomes 301 output bytes), so a legal payload of such numbers amplifies +//! ~60x and a large tool result could force multi-GiB allocations (Rust +//! aborts the host process on allocation failure — `catch_unwind` cannot stop +//! that). Encoding therefore aborts with [`EncodeError::OutputBudgetExceeded`] +//! once the output would exceed `max(2 x input bytes, 16KiB)`; the pipeline +//! maps that to `encoded: None` (fail-open, original payload kept). +//! Semantically safe: an encoding at >= 2x the input bytes can never win the +//! downstream token comparison, and skipping is closer to the old npm path, +//! which emitted compact exponent forms instead of expanding. Scalar writes +//! are pre-checked with their exact size (the crossing write is refused +//! outright, so the buffer cannot double its capacity past the budget); loop +//! bodies keep a post-write check as backstop for small structural output. +//! The pipeline adds two more anti-amplification layers on top of this +//! per-item budget: an aggregate batch budget and a per-item input size cap +//! (see `lib.rs`). + +use std::collections::{HashMap, HashSet}; +use std::fmt::Write as _; + +use crate::json::{JsonObject, JsonValue}; + +/// Mirrors `toon-format`'s `MAX_DEPTH`. Note the crate's depth counter is not +/// "one per nesting level": list-item object fields jump it by 2-3, so deeply +/// nested list shapes can exceed it and must fail exactly like the crate. +const MAX_DEPTH: usize = 256; + +/// Output budget floor (see module docs): small inputs may legitimately expand +/// past 2x (indentation, `[N]` markers), so the cap only starts binding above +/// this many output bytes. Also reused by the batch-level aggregate budget in +/// `lib.rs`. +pub(crate) const OUTPUT_BUDGET_FLOOR: usize = 16 * 1024; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) enum EncodeError { + /// The depth counter exceeded [`MAX_DEPTH`] (crate: `InvalidStructure`). + MaxDepthExceeded, + /// The output would exceed `max(2 x input bytes, 16KiB)` — deliberate + /// anti-amplification divergence from the crate (see module docs). + OutputBudgetExceeded, + /// std `Display` produced exponent notation for a float. Unreachable + /// (verified: Display always expands), but emitting it verbatim would + /// break byte parity with the crate's expansion, so it fails closed. + ExponentFloatFormat, + /// A container reached a primitive-only position. Unreachable — every call + /// site pre-checks — kept so a future bug fails closed like the crate's + /// `InvalidInput` instead of emitting corrupt output. + NonPrimitive, +} + +/// Encode a parsed JSON value as TOON, byte-identical to +/// `toon_format::encode_default` for every output within the anti-amplification +/// budget derived from `input_len` (the JSON text length; also used to presize +/// the output buffer). Scalar writes are pre-checked with their exact size, so +/// the accept set is precisely "final output length <= budget". +pub(crate) fn encode_to_toon( + value: &JsonValue<'_>, + input_len: usize, +) -> Result { + let budget = OUTPUT_BUDGET_FLOOR.max(input_len.saturating_mul(2)); + let mut encoder = Encoder { + out: String::with_capacity(input_len), + budget, + float_scratch: String::new(), + }; + encoder.value(value)?; + Ok(encoder.out) +} + +struct Encoder { + out: String, + /// Maximum output length; a write that would grow past it aborts the encode. + budget: usize, + /// Reused staging buffer for float formatting (exact-size budget pre-check + /// and trailing-zero trim happen here before the text reaches `out`). + float_scratch: String, +} + +impl Encoder { + fn value(&mut self, value: &JsonValue<'_>) -> Result<(), EncodeError> { + match value { + JsonValue::Array(arr) => self.array(None, arr, 0), + JsonValue::Object(obj) => self.object(obj, 0), + primitive => self.primitive(primitive), + } + } + + fn object(&mut self, obj: &JsonObject<'_>, depth: usize) -> Result<(), EncodeError> { + check_depth(depth)?; + for (i, (key, value)) in obj.iter().enumerate() { + if i > 0 { + self.out.push('\n'); + } + match value { + JsonValue::Array(arr) => self.array(Some(key), arr, depth)?, + JsonValue::Object(nested) => { + self.indent(depth); + self.key(key)?; + self.out.push(':'); + if !nested.is_empty() { + self.out.push('\n'); + self.object(nested, depth + 1)?; + } + } + primitive => { + self.indent(depth); + self.key(key)?; + self.out.push_str(": "); + self.primitive(primitive)?; + } + } + self.check_budget()?; + } + Ok(()) + } + + fn array( + &mut self, + key: Option<&str>, + arr: &[JsonValue<'_>], + depth: usize, + ) -> Result<(), EncodeError> { + check_depth(depth)?; + if arr.is_empty() { + self.array_header(key, 0, None, depth)?; + return Ok(()); + } + if let Some(fields) = tabular_fields(arr) { + self.array_header(key, arr.len(), Some(&fields), depth)?; + self.out.push('\n'); + self.tabular_rows(arr, &fields, depth + 1) + } else if arr.iter().all(is_primitive) { + self.primitive_array(key, arr, depth) + } else { + self.list_array(key, arr, depth) + } + } + + fn primitive_array( + &mut self, + key: Option<&str>, + arr: &[JsonValue<'_>], + depth: usize, + ) -> Result<(), EncodeError> { + self.array_header(key, arr.len(), None, depth)?; + self.out.push(' '); + for (i, value) in arr.iter().enumerate() { + if i > 0 { + self.out.push(','); + } + self.primitive(value)?; + } + Ok(()) + } + + /// One tabular row per array element, indented at `row_depth`. Rows almost + /// always repeat the header's key order, so the fast path writes values in + /// storage order. Set-equal-but-reordered rows go through a field→column + /// index built once per array (rows x fields hash lookups, the crate's own + /// complexity — a per-cell linear key search would be rows x fields^2). + fn tabular_rows( + &mut self, + arr: &[JsonValue<'_>], + fields: &[&str], + row_depth: usize, + ) -> Result<(), EncodeError> { + let mut field_columns: Option> = None; + let mut row_cells: Vec> = Vec::new(); + for (i, row) in arr.iter().enumerate() { + // Guaranteed an object by `tabular_fields`; skip mirrors the crate. + let Some(obj) = row.as_object() else { continue }; + self.indent(row_depth); + if obj.iter().map(|(key, _)| key).eq(fields.iter().copied()) { + for (j, (_, value)) in obj.iter().enumerate() { + if j > 0 { + self.out.push(','); + } + self.primitive(value)?; + } + } else { + let columns = field_columns.get_or_insert_with(|| { + fields + .iter() + .enumerate() + .map(|(column, &field)| (field, column)) + .collect() + }); + row_cells.clear(); + row_cells.resize(fields.len(), None); + for (key, value) in obj.iter() { + if let Some(&column) = columns.get(key) { + row_cells[column] = Some(value); + } + } + for (j, cell) in row_cells.iter().enumerate() { + if j > 0 { + self.out.push(','); + } + match cell { + Some(value) => self.primitive(value)?, + // Unreachable (detection verified every field), but the + // crate writes null for missing cells. + None => self.push_checked("null")?, + } + } + } + if i < arr.len() - 1 { + self.out.push('\n'); + } + self.check_budget()?; + } + Ok(()) + } + + /// List layout (`- item` lines) for arrays that are neither tabular nor + /// all-primitive. + fn list_array( + &mut self, + key: Option<&str>, + arr: &[JsonValue<'_>], + depth: usize, + ) -> Result<(), EncodeError> { + self.array_header(key, arr.len(), None, depth)?; + self.out.push('\n'); + for (i, item) in arr.iter().enumerate() { + self.indent(depth + 1); + self.out.push('-'); + match item { + JsonValue::Array(inner) => { + self.out.push(' '); + self.array(None, inner, depth + 1)?; + } + JsonValue::Object(obj) => self.list_item_object(obj, depth)?, + primitive => { + self.out.push(' '); + self.primitive(primitive)?; + } + } + if i < arr.len() - 1 { + self.out.push('\n'); + } + self.check_budget()?; + } + Ok(()) + } + + /// Object as a list item: first field on the hyphen line, remaining fields + /// two levels below it; empty objects are a bare hyphen. The uneven depth + /// jumps (+2/+3) replicate the crate's layout exactly. + fn list_item_object(&mut self, obj: &JsonObject<'_>, depth: usize) -> Result<(), EncodeError> { + let mut entries = obj.iter(); + let Some((first_key, first_value)) = entries.next() else { + return Ok(()); + }; + self.out.push(' '); + match first_value { + JsonValue::Array(arr) => { + self.key(first_key)?; + if let Some(fields) = tabular_fields(arr) { + // Tabular rows of a first-field array sit at depth + 3 + // relative to this list's header (crate quirk, no depth + // check on this path). + self.array_header(None, arr.len(), Some(&fields), 0)?; + self.out.push('\n'); + self.tabular_rows(arr, &fields, depth + 3)?; + } else { + self.array(None, arr, depth + 2)?; + } + } + JsonValue::Object(nested) => { + self.key(first_key)?; + self.out.push(':'); + if !nested.is_empty() { + self.out.push('\n'); + self.object(nested, depth + 3)?; + } + } + primitive => { + self.key(first_key)?; + self.out.push_str(": "); + self.primitive(primitive)?; + } + } + for (key, value) in entries { + self.out.push('\n'); + self.indent(depth + 2); + match value { + JsonValue::Array(arr) => { + self.key(key)?; + self.array(None, arr, depth + 2)?; + } + JsonValue::Object(nested) => { + self.key(key)?; + self.out.push(':'); + if !nested.is_empty() { + self.out.push('\n'); + self.object(nested, depth + 3)?; + } + } + primitive => { + self.key(key)?; + self.out.push_str(": "); + self.primitive(primitive)?; + } + } + self.check_budget()?; + } + Ok(()) + } + + /// `key[N]:`, `[N]:` or `key[N]{f1,f2}:`. Indent is written only when a + /// key is present (crate behavior — keyless headers are always inline). + fn array_header( + &mut self, + key: Option<&str>, + len: usize, + fields: Option<&[&str]>, + depth: usize, + ) -> Result<(), EncodeError> { + if let Some(key) = key { + self.indent(depth); + self.key(key)?; + } + self.out.push('['); + self.write_int(len)?; + self.out.push(']'); + if let Some(fields) = fields { + self.out.push('{'); + for (i, &field) in fields.iter().enumerate() { + if i > 0 { + self.out.push(','); + } + self.key(field)?; + } + self.out.push('}'); + } + self.out.push(':'); + Ok(()) + } + + fn primitive(&mut self, value: &JsonValue<'_>) -> Result<(), EncodeError> { + match value { + JsonValue::Null => self.push_checked("null"), + JsonValue::Bool(true) => self.push_checked("true"), + JsonValue::Bool(false) => self.push_checked("false"), + // Integer typing follows the parse (see crate::json); formatting + // matches the crate's as_i64 → as_u64 fallback digit-for-digit. + JsonValue::PosInt(u) => self.write_int(*u), + JsonValue::NegInt(i) => self.write_int(*i), + JsonValue::Float(f) => self.f64_canonical(*f), + JsonValue::String(s) => self.string_value(s), + JsonValue::Array(_) | JsonValue::Object(_) => Err(EncodeError::NonPrimitive), + } + } + + /// Append `text` only if the result stays within the output budget; the + /// crossing write is refused BEFORE it lands, so the buffer never grows + /// (or reallocates) past the budget on a scalar. `len + text.len() > + /// budget` is exactly the post-write condition, so the accept set is + /// unchanged versus checking afterwards. + fn push_checked(&mut self, text: &str) -> Result<(), EncodeError> { + if self.out.len() + text.len() > self.budget { + return Err(EncodeError::OutputBudgetExceeded); + } + self.out.push_str(text); + Ok(()) + } + + /// Backstop for the small structural output (indent, punctuation, `- `) + /// that is not routed through [`Self::push_checked`]. Called at the end of + /// every object entry / array row / list item. + fn check_budget(&self) -> Result<(), EncodeError> { + if self.out.len() > self.budget { + return Err(EncodeError::OutputBudgetExceeded); + } + Ok(()) + } + + /// Canonical TOON float formatting, mirroring the crate's + /// `format_canonical_number` for `Number::Float`: + /// 1. integer-valued floats in i64/u64 range print as integers (this also + /// turns -0.0 into "0", covering the crate's normalize step). Note the + /// crate's `as_i64` saturating-cast check has no `i64::MAX` exclusion, + /// so the float 2^63 prints as 9223372036854775807 (and, via the same + /// saturation in `as_u64`, 2^64 prints as u64::MAX) — replicated, the + /// boundary parity test pins it; + /// 2. everything else is std `Display` (shortest repr, and — verified — + /// never exponent notation, so the crate's exponent-expansion fallback + /// is unreachable) with the crate's trailing-zero trim applied. + fn f64_canonical(&mut self, f: f64) -> Result<(), EncodeError> { + let i = f as i64; + if i as f64 == f { + return self.write_int(i); + } + if f >= 0.0 { + let u = f as u64; + if u as f64 == f { + return self.write_int(u); + } + } + // Stage the Display text in the reused scratch buffer so the budget + // pre-check sees the exact final size. + let mut scratch = std::mem::take(&mut self.float_scratch); + scratch.clear(); + let _ = write!(scratch, "{f}"); + // Fail closed if std Display ever produced exponent notation: the + // crate expands exponents, so emitting this verbatim would break byte + // parity, and the trim below would mangle it. Unreachable today + // (verified empirically), guarded at runtime so a release build + // returns None instead of corrupt bytes. + let result = if scratch.contains(['e', 'E']) { + Err(EncodeError::ExponentFloatFormat) + } else { + trim_trailing_zeros(&mut scratch); + self.push_checked(&scratch) + }; + self.float_scratch = scratch; + result + } + + fn string_value(&mut self, s: &str) -> Result<(), EncodeError> { + if needs_quoting(s) { + self.quoted(s) + } else { + self.push_checked(s) + } + } + + fn key(&mut self, key: &str) -> Result<(), EncodeError> { + if is_valid_unquoted_key(key) { + self.push_checked(key) + } else { + self.quoted(key) + } + } + + /// Quoted string: only `\n \r \t " \` are escaped; every other character + /// (including other control characters) passes through raw. The exact + /// escaped size (each escape adds one byte, plus two quotes) is checked + /// against the budget before anything is written. + fn quoted(&mut self, s: &str) -> Result<(), EncodeError> { + let escapes = s + .bytes() + .filter(|b| matches!(b, b'\n' | b'\r' | b'\t' | b'"' | b'\\')) + .count(); + if self.out.len() + s.len() + escapes + 2 > self.budget { + return Err(EncodeError::OutputBudgetExceeded); + } + self.out.push('"'); + let mut plain_from = 0; + for (i, b) in s.bytes().enumerate() { + let escaped = match b { + b'\n' => "\\n", + b'\r' => "\\r", + b'\t' => "\\t", + b'"' => "\\\"", + b'\\' => "\\\\", + _ => continue, + }; + self.out.push_str(&s[plain_from..i]); + self.out.push_str(escaped); + plain_from = i + 1; + } + self.out.push_str(&s[plain_from..]); + self.out.push('"'); + Ok(()) + } + + fn indent(&mut self, depth: usize) { + const CHUNK: &str = " "; // 32 spaces + let mut remaining = depth * 2; + while remaining >= CHUNK.len() { + self.out.push_str(CHUNK); + remaining -= CHUNK.len(); + } + self.out.push_str(&CHUNK[..remaining]); + } + + fn write_int(&mut self, value: impl itoa::Integer) -> Result<(), EncodeError> { + let mut buf = itoa::Buffer::new(); + self.push_checked(buf.format(value)) + } +} + +/// Crate's `remove_trailing_zeros`: trim trailing zeros after a single decimal +/// point, dropping the point when the fraction empties. (Shortest-repr Display +/// output makes this a no-op, but the crate applies it to every non-integer +/// float, so we mirror it.) +fn trim_trailing_zeros(text: &mut String) { + let end = { + let bytes = text.as_bytes(); + let Some(dot) = bytes.iter().position(|&b| b == b'.') else { + return; + }; + if bytes[dot + 1..].contains(&b'.') { + return; // multiple dots: crate returns the string unchanged + } + let mut end = bytes.len(); + while end > dot + 1 && bytes[end - 1] == b'0' { + end -= 1; + } + if end == dot + 1 { dot } else { end } + }; + text.truncate(end); +} + +fn check_depth(depth: usize) -> Result<(), EncodeError> { + if depth > MAX_DEPTH { + return Err(EncodeError::MaxDepthExceeded); + } + Ok(()) +} + +fn is_primitive(value: &JsonValue<'_>) -> bool { + !matches!(value, JsonValue::Array(_) | JsonValue::Object(_)) +} + +/// Tabular detection: every element is an object with only primitive values, +/// the same field count as the first element, and (order-insensitively) the +/// first element's field set. Returns the first element's fields in order. +/// +/// Order-insensitive comparison is O(total fields): a field set is built once +/// per array (lazily, on the first out-of-order row) and each row's keys are +/// checked against it. Row keys are unique (the parser deduplicates) and the +/// counts match, so "every row key is a header field" is exactly the crate's +/// "every header field is present in the row". +fn tabular_fields<'v>(arr: &'v [JsonValue<'_>]) -> Option> { + let first = arr.first()?.as_object()?; + if !first.iter().all(|(_, value)| is_primitive(value)) { + return None; + } + let fields: Vec<&str> = first.iter().map(|(key, _)| key).collect(); + let mut field_set: Option> = None; + for row in &arr[1..] { + let obj = row.as_object()?; + if obj.len() != fields.len() { + return None; + } + // Fast path: identical field order; otherwise compare key sets. + if !obj.iter().map(|(key, _)| key).eq(fields.iter().copied()) { + let set = field_set.get_or_insert_with(|| fields.iter().copied().collect()); + if !obj.iter().all(|(key, _)| set.contains(key)) { + return None; + } + } + if !obj.iter().all(|(_, value)| is_primitive(value)) { + return None; + } + } + Some(fields) +} + +/// A string value can stay unquoted only if it cannot be misread as a literal, +/// a number, or structure. Comma is the only delimiter we emit, so the crate's +/// object/array quoting contexts collapse into one predicate. +fn needs_quoting(s: &str) -> bool { + if s.is_empty() { + return true; + } + if matches!(s, "null" | "true" | "false") { + return true; + } + if is_numeric_like(s) { + return true; + } + // Structural chars (note: '-' anywhere), escapes, delimiter, escaped + // whitespace — all single ASCII bytes, so a byte scan is exact on UTF-8. + if s.bytes().any(|b| { + matches!( + b, + b'[' | b']' | b'{' | b'}' | b':' | b'-' | b'\\' | b'"' | b',' | b'\n' | b'\r' | b'\t' + ) + }) { + return true; + } + if s.starts_with(char::is_whitespace) || s.ends_with(char::is_whitespace) { + return true; + } + // Leading zero followed by a digit reads as a malformed number. + s.starts_with('0') && s[1..].starts_with(|c: char| c.is_ascii_digit()) +} + +/// Crate's `is_numeric_like`: optional leading '-', then a digit (no leading +/// zeros), then only `[0-9.eE+-]`. Byte-level scan is exact: every significant +/// character is ASCII and multi-byte UTF-8 units never match ASCII patterns. +fn is_numeric_like(s: &str) -> bool { + let digits = s.strip_prefix('-').unwrap_or(s).as_bytes(); + let Some(&first) = digits.first() else { + return false; + }; + if !first.is_ascii_digit() { + return false; + } + if first == b'0' && digits.get(1).is_some_and(u8::is_ascii_digit) { + return false; + } + digits + .iter() + .all(|&b| b.is_ascii_digit() || matches!(b, b'.' | b'e' | b'E' | b'+' | b'-')) +} + +/// Unquoted keys: alphabetic (Unicode) or '_' first, then alphanumeric +/// (Unicode), '_' or '.'. +fn is_valid_unquoted_key(key: &str) -> bool { + let mut chars = key.chars(); + let Some(first) = chars.next() else { + return false; + }; + if !first.is_alphabetic() && first != '_' { + return false; + } + chars.all(|c| c.is_alphanumeric() || c == '_' || c == '.') +} + +#[cfg(test)] +mod tests { + use std::fmt::Write as _; + + use crate::json::parse_json; + + use super::*; + + fn encode(raw: &str) -> String { + let value = parse_json(raw).expect("test input parses"); + encode_to_toon(&value, raw.len()).expect("encodes") + } + + #[test] + fn quoting_rules() { + for quoted in [ + "", "null", "true", "false", "123", "0", "-5", "3.14", "1e10", "0.5", "07", "a-b", + "-x", "a,b", "a:b", "a[b", "a]b", "a{b", "a}b", "a\"b", "a\\b", "a\nb", "a\rb", "a\tb", + " x", "x ", "\u{a0}x", "x\u{a0}", "1.2.3", + ] { + assert!(needs_quoting(quoted), "expected quoting for {quoted:?}"); + } + for unquoted in ["hello", "hello world", "0x", "+5", "1a2", "a.b", "x0", "é"] { + assert!( + !needs_quoting(unquoted), + "expected no quoting for {unquoted:?}" + ); + } + } + + #[test] + fn key_rules() { + for ok in ["a", "_", "key.name.sub", "key.", "Ключ", "k1_2.x"] { + assert!(is_valid_unquoted_key(ok), "expected unquoted key {ok:?}"); + } + for quoted in ["", "1a", ".a", "a-b", "a b", "a:b", "a[b]"] { + assert!( + !is_valid_unquoted_key(quoted), + "expected quoted key {quoted:?}" + ); + } + } + + #[test] + fn float_formatting_matches_canonical_rules() { + for (raw, expected) in [ + (r#"{"x": 1.0}"#, "x: 1"), + (r#"{"x": -0.0}"#, "x: 0"), + (r#"{"x": -0}"#, "x: 0"), + (r#"{"x": 0.1}"#, "x: 0.1"), + (r#"{"x": 1e-7}"#, "x: 0.0000001"), + (r#"{"x": 1.5}"#, "x: 1.5"), + // 2^63: the crate's saturating as_i64 cast prints i64::MAX here + // (documented parity quirk, see f64_canonical). + (r#"{"x": 9.223372036854776e18}"#, "x: 9223372036854775807"), + (r#"{"x": -9.223372036854776e18}"#, "x: -9223372036854775808"), + ] { + assert_eq!(encode(raw), expected, "raw: {raw}"); + } + let huge = encode(r#"{"x": 1e300}"#); + assert!(huge.starts_with("x: 1")); + assert_eq!(huge.len(), "x: ".len() + 301); + } + + #[test] + fn empty_object_array_quirk() { + assert_eq!(encode("[{}, {}]"), "[2]{}:\n \n "); + assert_eq!(encode(r#"{"a": [{}]}"#), "a[1]{}:\n "); + } + + #[test] + fn structure_layouts() { + assert_eq!(encode("{}"), ""); + assert_eq!(encode("[]"), "[0]:"); + assert_eq!(encode(r#"{"a": {}}"#), "a:"); + assert_eq!(encode(r#"[1, "two", null]"#), "[3]: 1,two,null"); + assert_eq!( + encode(r#"{"users": [{"id": 1, "n": "a"}, {"id": 2, "n": "b"}]}"#), + "users[2]{id,n}:\n 1,a\n 2,b" + ); + assert_eq!( + encode(r#"[{"rows": [{"id": 1}], "total": 1}, "tail"]"#), + "[2]:\n - rows[1]{id}:\n 1\n total: 1\n - tail" + ); + assert_eq!( + encode(r#"[[1, 2], {"a": {"b": 1}}]"#), + "[2]:\n - [2]: 1,2\n - a:\n b: 1" + ); + } + + #[test] + fn reordered_tabular_rows_use_header_field_order() { + assert_eq!( + encode(r#"[{"a": 1, "b": 2}, {"b": 4, "a": 3}]"#), + "[2]{a,b}:\n 1,2\n 3,4" + ); + } + + /// Adversarial reordered-tabular shape (10k rows x 30 keys, odd rows in + /// reversed key order): output must match the crate byte-for-byte, and the + /// field→column index keeps it rows x keys instead of rows x keys^2. + #[test] + fn reordered_tabular_rows_match_crate_at_scale() { + const ROWS: usize = 10_000; + const KEYS: usize = 30; + let mut json = String::from("["); + for row in 0..ROWS { + if row > 0 { + json.push(','); + } + json.push('{'); + let order: Vec = if row % 2 == 0 { + (0..KEYS).collect() + } else { + (0..KEYS).rev().collect() + }; + for (j, k) in order.into_iter().enumerate() { + if j > 0 { + json.push(','); + } + let _ = write!(json, r#""k{k:02}":{}"#, row * 31 + k); + } + json.push('}'); + } + json.push(']'); + + let ours = encode(&json); + let parsed: serde_json::Value = serde_json::from_str(&json).expect("oracle parse"); + let expected = toon_format::encode_default(&parsed).expect("oracle encodes"); + assert_eq!(ours, expected); + } + + /// Detection stress: 2 rows x 5k keys with the second row fully reversed. + /// Set-based detection must stay O(total fields) — a per-field linear + /// lookup would be 25M comparisons here — and the wide objects also + /// exercise the parser's large-object duplicate index. + #[test] + fn wide_reversed_tabular_detection_matches_crate() { + const KEYS: usize = 5_000; + let mut json = String::from("[{"); + for k in 0..KEYS { + if k > 0 { + json.push(','); + } + let _ = write!(json, r#""k{k:04}":{k}"#); + } + json.push_str("},{"); + for (j, k) in (0..KEYS).rev().enumerate() { + if j > 0 { + json.push(','); + } + let _ = write!(json, r#""k{k:04}":{k}"#); + } + json.push_str("}]"); + + let ours = encode(&json); + let parsed: serde_json::Value = serde_json::from_str(&json).expect("oracle parse"); + let expected = toon_format::encode_default(&parsed).expect("oracle encodes"); + assert_eq!(ours, expected); + } + + /// Targeted i64/u64/f64 boundary parity against the crate. + #[test] + fn boundary_number_parity_with_crate() { + for raw in [ + "-9223372036854775808", // i64::MIN + "9223372036854775807", // i64::MAX + "18446744073709551615", // u64::MAX + "9007199254740991", // 2^53 - 1 + "9007199254740992", // 2^53 + "9007199254740993", // 2^53 + 1 (exact as i64) + "9007199254740993.0", // 2^53 + 1 as float (rounds to 2^53) + "9223372036854775808.0", // 2^63 as float (crate as_i64 saturation) + "18446744073709551616.0", // 2^64 as float (crate as_u64 saturation) + "-9223372036854775808.0", // -2^63 as float (i64 path) + "5e-324", // smallest subnormal + "2.2250738585072014e-308", // smallest normal + "1.7976931348623157e308", // f64::MAX + "-0.0", + "-0", + "0.1", + "1e-7", + "1e300", + "-1e300", + "1.0", + "-1.0", + "3.141592653589793", + ] { + let ours = encode(raw); + let parsed: serde_json::Value = serde_json::from_str(raw).expect("oracle parse"); + let expected = toon_format::encode_default(&parsed).expect("oracle encodes"); + assert_eq!(ours, expected, "raw: {raw}"); + } + } + + #[test] + fn exponent_amplification_aborts_within_budget() { + // ~72KB of exponent-form numbers would expand ~50x (each 1e300 is 5 + // JSON bytes but 301 output bytes); the encode must abort instead. + let large = format!("[{}]", vec!["1e300"; 12_000].join(",")); + let value = parse_json(&large).expect("parses"); + assert_eq!( + encode_to_toon(&value, large.len()), + Err(EncodeError::OutputBudgetExceeded) + ); + } + + #[test] + fn output_budget_boundary_encodes_below_and_aborts_above() { + // Small inputs bind at the 16KiB floor: 50 x 1e300 -> ~15.1KB output + // (still byte-identical to the crate), 60 x -> ~18.1KB trips it. + let under = format!("[{}]", vec!["1e300"; 50].join(",")); + let parsed: serde_json::Value = serde_json::from_str(&under).expect("oracle parse"); + assert_eq!( + encode(&under), + toon_format::encode_default(&parsed).expect("oracle encodes") + ); + + let over = format!("[{}]", vec!["1e300"; 60].join(",")); + let value = parse_json(&over).expect("parses"); + assert_eq!( + encode_to_toon(&value, over.len()), + Err(EncodeError::OutputBudgetExceeded) + ); + let parsed: serde_json::Value = serde_json::from_str(&over).expect("oracle parse"); + let crate_len = toon_format::encode_default(&parsed) + .expect("oracle encodes") + .len(); + assert!( + crate_len > OUTPUT_BUDGET_FLOOR.max(2 * over.len()), + "the skipped output must genuinely exceed the budget" + ); + } + + #[test] + fn budget_abort_keeps_buffer_bounded() { + // 100k exponent floats would expand to ~30MB; the encoder must stop + // at the budget instead of building the whole string. Scalar writes + // are pre-checked, so the length never exceeds the budget at all, and + // capacity growth (amortized doubling) stays within 2x of it. + let numbers = vec![JsonValue::Float(1e300); 100_000]; + let mut encoder = Encoder { + out: String::new(), + budget: OUTPUT_BUDGET_FLOOR, + float_scratch: String::new(), + }; + assert_eq!( + encoder.array(None, &numbers, 0), + Err(EncodeError::OutputBudgetExceeded) + ); + assert!( + encoder.out.len() <= OUTPUT_BUDGET_FLOOR, + "len={}", + encoder.out.len() + ); + assert!( + encoder.out.capacity() <= 2 * OUTPUT_BUDGET_FLOOR, + "capacity={}", + encoder.out.capacity() + ); + } + + #[test] + fn deep_list_nesting_exceeds_max_depth() { + // Built by hand: through `parse_json` the serde_json 128-level + // recursion limit rejects the document first, so encode-side depth + // failure only guards manually constructed values — exactly like the + // crate, which we assert as the oracle. + let mut value = JsonValue::String("leaf".into()); + let mut oracle = serde_json::json!("leaf"); + for _ in 0..200 { + let mut obj = JsonObject::default(); + obj.push_for_tests("first", JsonValue::PosInt(1)); + obj.push_for_tests("k", value); + value = JsonValue::Array(vec![JsonValue::Object(obj)]); + oracle = serde_json::json!([{ "first": 1, "k": oracle }]); + } + // Large input_len: the output budget must not fire before the depth + // check does (there is no real input text for a hand-built DOM). + assert_eq!( + encode_to_toon(&value, 1 << 20), + Err(EncodeError::MaxDepthExceeded) + ); + assert!( + toon_format::encode_default(&oracle).is_err(), + "oracle must reject the same depth" + ); + } +} diff --git a/platform/archestra-rs/proxy-transform-core/src/json.rs b/platform/archestra-rs/proxy-transform-core/src/json.rs new file mode 100644 index 00000000000..675be929081 --- /dev/null +++ b/platform/archestra-rs/proxy-transform-core/src/json.rs @@ -0,0 +1,369 @@ +//! Minimal borrowed JSON DOM for the TOON kernel. `serde_json::Value` was the +//! hot spot of the pipeline (per-key IndexMap hashing plus an owned `String` +//! per key and string); this DOM parses ~3x faster by borrowing every +//! escape-free string/key from the input buffer and storing object entries in +//! insertion order in a plain `Vec`. +//! +//! Parity contract (the encoder's byte-parity oracle parses with +//! `serde_json::Value`, so this DOM must be observationally identical): +//! - numbers keep `serde_json`'s exact typing — the deserializer picks +//! `visit_u64`/`visit_i64`/`visit_f64` and we store what it hands us; +//! - duplicate object keys replicate `IndexMap::insert`, which JS `JSON.parse` +//! also matches: the key keeps its first position, the value is replaced; +//! - `parse_json` fails on trailing content (like `serde_json::from_str`) and +//! inherits `serde_json`'s 128-level recursion limit. +//! +//! Memory bound: strings and keys containing escapes force owned `Cow` +//! allocations (an input of millions of tiny escaped strings means millions of +//! tiny allocations), and every enum slot costs DOM overhead. Both are bounded +//! by the pipeline's per-item input cap (`MAX_ITEM_INPUT_BYTES` in `lib.rs`): +//! oversized items are never parsed at all. + +use std::borrow::Cow; +use std::collections::HashMap; +use std::collections::hash_map::Entry; +use std::fmt; + +use serde::de::{DeserializeSeed, MapAccess, SeqAccess, Visitor}; + +#[derive(Clone, Debug, PartialEq)] +pub(crate) enum JsonValue<'a> { + Null, + Bool(bool), + /// Non-negative integer literal (`visit_u64`). + PosInt(u64), + /// Negative integer literal (`visit_i64`). + NegInt(i64), + /// Everything else, always finite (`visit_f64`). + Float(f64), + String(Cow<'a, str>), + Array(Vec>), + Object(JsonObject<'a>), +} + +impl<'a> JsonValue<'a> { + pub(crate) fn as_object(&self) -> Option<&JsonObject<'a>> { + match self { + JsonValue::Object(obj) => Some(obj), + _ => None, + } + } + + pub(crate) fn as_str(&self) -> Option<&str> { + match self { + JsonValue::String(text) => Some(text), + _ => None, + } + } +} + +/// Object entries in first-occurrence order. +#[derive(Clone, Debug, PartialEq, Default)] +pub(crate) struct JsonObject<'a> { + entries: Vec<(Cow<'a, str>, JsonValue<'a>)>, +} + +impl<'a> JsonObject<'a> { + pub(crate) fn len(&self) -> usize { + self.entries.len() + } + + pub(crate) fn is_empty(&self) -> bool { + self.entries.is_empty() + } + + pub(crate) fn iter(&self) -> impl ExactSizeIterator)> { + self.entries + .iter() + .map(|(key, value)| (key.as_ref(), value)) + } + + /// Linear-scan lookup: only used on cold paths (wrapper detection on a + /// first element, reordered tabular rows), never per-key on hot loops. + pub(crate) fn get(&self, key: &str) -> Option<&JsonValue<'a>> { + self.entries + .iter() + .find(|(entry_key, _)| entry_key == key) + .map(|(_, value)| value) + } + + pub(crate) fn get_mut(&mut self, key: &str) -> Option<&mut JsonValue<'a>> { + self.entries + .iter_mut() + .find(|(entry_key, _)| entry_key == key) + .map(|(_, value)| value) + } + + /// Direct entry construction for tests that need values deeper than the + /// parser's recursion limit. Skips duplicate handling. + #[cfg(test)] + pub(crate) fn push_for_tests(&mut self, key: &'a str, value: JsonValue<'a>) { + self.entries.push((Cow::Borrowed(key), value)); + } +} + +/// Parse a complete JSON document, rejecting trailing content — the same +/// accept set as `serde_json::from_str::`. +pub(crate) fn parse_json(input: &str) -> Option> { + let mut deserializer = serde_json::Deserializer::from_str(input); + let value = ValueSeed.deserialize(&mut deserializer).ok()?; + deserializer.end().ok()?; + Some(value) +} + +// === parsing internals === + +struct ValueSeed; + +impl<'de> DeserializeSeed<'de> for ValueSeed { + type Value = JsonValue<'de>; + + fn deserialize>( + self, + deserializer: D, + ) -> Result { + deserializer.deserialize_any(ValueSeed) + } +} + +impl<'de> Visitor<'de> for ValueSeed { + type Value = JsonValue<'de>; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("any JSON value") + } + + fn visit_unit(self) -> Result { + Ok(JsonValue::Null) + } + + fn visit_bool(self, value: bool) -> Result { + Ok(JsonValue::Bool(value)) + } + + fn visit_u64(self, value: u64) -> Result { + Ok(JsonValue::PosInt(value)) + } + + fn visit_i64(self, value: i64) -> Result { + Ok(JsonValue::NegInt(value)) + } + + fn visit_f64(self, value: f64) -> Result { + Ok(JsonValue::Float(value)) + } + + fn visit_borrowed_str(self, value: &'de str) -> Result { + Ok(JsonValue::String(Cow::Borrowed(value))) + } + + fn visit_str(self, value: &str) -> Result { + Ok(JsonValue::String(Cow::Owned(value.to_owned()))) + } + + fn visit_string(self, value: String) -> Result { + Ok(JsonValue::String(Cow::Owned(value))) + } + + fn visit_seq>(self, mut seq: A) -> Result { + let mut items = Vec::new(); + while let Some(item) = seq.next_element_seed(ValueSeed)? { + items.push(item); + } + Ok(JsonValue::Array(items)) + } + + fn visit_map>(self, mut map: A) -> Result { + let mut builder = ObjectBuilder::default(); + while let Some(key) = map.next_key_seed(KeySeed)? { + let value = map.next_value_seed(ValueSeed)?; + builder.insert(key, value); + } + Ok(JsonValue::Object(JsonObject { + entries: builder.entries, + })) + } +} + +struct KeySeed; + +impl<'de> DeserializeSeed<'de> for KeySeed { + type Value = Cow<'de, str>; + + fn deserialize>( + self, + deserializer: D, + ) -> Result { + deserializer.deserialize_str(KeySeed) + } +} + +impl<'de> Visitor<'de> for KeySeed { + type Value = Cow<'de, str>; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("an object key") + } + + fn visit_borrowed_str(self, value: &'de str) -> Result { + Ok(Cow::Borrowed(value)) + } + + fn visit_str(self, value: &str) -> Result { + Ok(Cow::Owned(value.to_owned())) + } + + fn visit_string(self, value: String) -> Result { + Ok(Cow::Owned(value)) + } +} + +/// Above this size, duplicate detection switches from a linear scan to a lazy +/// hash index so pathological many-key objects stay O(n). +const LINEAR_DEDUP_MAX: usize = 32; + +#[derive(Default)] +struct ObjectBuilder<'a> { + entries: Vec<(Cow<'a, str>, JsonValue<'a>)>, + /// Key → entry index, built lazily for large objects. SipHash (std + /// default) keeps adversarial key sets collision-resistant. Map keys are + /// clones of the entry keys: free for borrowed keys (the common case), + /// one String copy for escaped ones. + index: Option, usize>>, +} + +impl<'a> ObjectBuilder<'a> { + /// `IndexMap::insert` semantics: an existing key keeps its position and + /// gets the new value; a new key is appended. + fn insert(&mut self, key: Cow<'a, str>, value: JsonValue<'a>) { + if self.index.is_none() && self.entries.len() >= LINEAR_DEDUP_MAX { + self.index = Some( + self.entries + .iter() + .enumerate() + .map(|(i, (existing, _))| (existing.clone(), i)) + .collect(), + ); + } + match &mut self.index { + Some(index) => match index.entry(key.clone()) { + Entry::Occupied(slot) => { + self.entries[*slot.get()].1 = value; + } + Entry::Vacant(slot) => { + slot.insert(self.entries.len()); + self.entries.push((key, value)); + } + }, + None => { + if let Some(entry) = self + .entries + .iter_mut() + .find(|(existing, _)| *existing == key) + { + entry.1 = value; + return; + } + self.entries.push((key, value)); + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parses_all_value_kinds() { + let value = parse_json(r#"{"a":null,"b":true,"c":18446744073709551615,"d":-3,"e":1.5,"f":"x\ny","g":[1],"h":{}}"#) + .expect("parses"); + let JsonValue::Object(obj) = value else { + panic!("expected object"); + }; + assert_eq!(obj.get("a"), Some(&JsonValue::Null)); + assert_eq!(obj.get("b"), Some(&JsonValue::Bool(true))); + assert_eq!(obj.get("c"), Some(&JsonValue::PosInt(u64::MAX))); + assert_eq!(obj.get("d"), Some(&JsonValue::NegInt(-3))); + assert_eq!(obj.get("e"), Some(&JsonValue::Float(1.5))); + assert_eq!( + obj.get("f"), + Some(&JsonValue::String(Cow::Owned("x\ny".to_string()))) + ); + assert_eq!( + obj.get("g"), + Some(&JsonValue::Array(vec![JsonValue::PosInt(1)])) + ); + assert_eq!( + obj.get("h"), + Some(&JsonValue::Object(JsonObject::default())) + ); + } + + #[test] + fn escape_free_strings_borrow_from_input() { + let input = r#"{"key":"plain value"}"#; + let value = parse_json(input).expect("parses"); + let JsonValue::Object(obj) = value else { + panic!("expected object"); + }; + let Some(JsonValue::String(text)) = obj.get("key") else { + panic!("expected string"); + }; + assert!(matches!(text, Cow::Borrowed(_))); + } + + #[test] + fn duplicate_keys_keep_first_position_and_last_value() { + // Same semantics as serde_json's preserve_order IndexMap and JS + // JSON.parse: first position wins, last value wins. + let value = parse_json(r#"{"a":1,"b":2,"a":3}"#).expect("parses"); + let JsonValue::Object(obj) = value else { + panic!("expected object"); + }; + let entries: Vec<_> = obj.iter().collect(); + assert_eq!( + entries, + vec![("a", &JsonValue::PosInt(3)), ("b", &JsonValue::PosInt(2))] + ); + } + + #[test] + fn duplicate_keys_dedup_in_large_objects() { + let mut input = String::from("{"); + for i in 0..100 { + input.push_str(&format!(r#""k{i}":{i},"#)); + } + input.push_str(r#""k7":777}"#); + let value = parse_json(&input).expect("parses"); + let JsonValue::Object(obj) = value else { + panic!("expected object"); + }; + assert_eq!(obj.len(), 100); + assert_eq!(obj.get("k7"), Some(&JsonValue::PosInt(777))); + assert_eq!(obj.iter().nth(7), Some(("k7", &JsonValue::PosInt(777)))); + } + + #[test] + fn rejects_trailing_content_and_invalid_json() { + for bad in [ + "{} garbage", + "[1,2] 3", + "", + "{'a':1}", + "[1,", + "NaN", + "1e400", + ] { + assert!(parse_json(bad).is_none(), "expected reject: {bad:?}"); + } + } + + #[test] + fn respects_serde_json_recursion_limit() { + let deep_ok = format!("{}1{}", "[".repeat(127), "]".repeat(127)); + assert!(parse_json(&deep_ok).is_some()); + let too_deep = format!("{}1{}", "[".repeat(129), "]".repeat(129)); + assert!(parse_json(&too_deep).is_none()); + } +} diff --git a/platform/archestra-rs/proxy-transform-core/src/lib.rs b/platform/archestra-rs/proxy-transform-core/src/lib.rs index ab62b732e04..f87176fae06 100644 --- a/platform/archestra-rs/proxy-transform-core/src/lib.rs +++ b/platform/archestra-rs/proxy-transform-core/src/lib.rs @@ -1,12 +1,21 @@ //! Pure tool-result transformation kernel for the LLM proxy: unwrap the text-block //! wrapper some clients (n8n, Vercel AI SDK) add around tool results, parse the -//! JSON, and encode it as TOON (spec v3, official `toon-format` crate). +//! JSON, and encode it as TOON (spec v3, own linear encoder in [`encode`], +//! byte-compatible with the `toon-format` crate, which remains the test oracle). //! //! Node-free; the NAPI adapter lives in `proxy_transform_rs`. Per-item processing //! is infallible: content that is not parseable JSON yields `encoded: None` and the //! adapter keeps the original payload (fail-open, exactly like the TS path today). +//! +//! Anti-amplification limits (all fail-open to `encoded: None`): a per-item +//! output budget (see [`encode`] module docs), an aggregate batch output budget +//! (see [`toon_encode_tool_results`]), and a per-item input size cap +//! ([`MAX_ITEM_INPUT_BYTES`]). + +mod encode; +mod json; -use serde_json::Value; +use json::JsonValue; /// One tool result to transform. `id` is the provider tool id, carried for /// logging only — it is not unique across items (Anthropic reuses one @@ -23,7 +32,9 @@ pub struct ToonEncodeItem { /// The transformation output for one item. `normalized` is the unwrapped string /// when unwrapping was requested and matched, else the original `raw_content` /// (adapters tokenize it for accounting). `encoded` is the TOON encoding, or -/// `None` when the content is not parseable JSON. +/// `None` when the content is not parseable JSON — or when encoding would +/// exceed the anti-amplification output budget (see `encode` module docs); +/// adapters keep the original payload either way. /// /// `use_nullable` makes `encoded: None` cross the boundary as an explicit JS /// `null` (typed `string | null`) instead of an omitted key. @@ -34,32 +45,114 @@ pub struct ToonEncodeResult { pub encoded: Option, } +/// Items whose raw content exceeds this many bytes are not parsed or encoded +/// at all (`encoded: None`, content kept verbatim, unwrap skipped too). +/// Rationale: multi-MB tool results gain nothing from TOON compression, and +/// the parser DOM expands to roughly 12-24x the input bytes — without the cap +/// a single large item could allocation-abort the host process before any +/// encoder output budget applies. The cap bounds parser memory per item (and +/// so per AsyncTask) to ~cap x expansion. +pub const MAX_ITEM_INPUT_BYTES: usize = 10 * 1024 * 1024; + /// Transform a batch of tool results. Positional contract: the output has the /// same length and order as the input. Never panics on any input. +/// +/// Aggregate anti-amplification budget: per-item output budgets have a 16KiB +/// floor, which many small exponent-heavy items could otherwise sum into +/// unbounded retained output (200k tiny items x ~16KiB each). The batch's +/// total produced output is capped at `2 x total input bytes + one floor`; +/// once the running total exceeds it, remaining items are not encoded +/// (`encoded: None`, fail-open like the per-item budget, unwrap still applied). pub fn toon_encode_tool_results(items: Vec) -> Vec { - items.into_iter().map(encode_item).collect() + let total_input: usize = items.iter().map(|item| item.raw_content.len()).sum(); + let batch_budget = total_input + .saturating_mul(2) + .saturating_add(encode::OUTPUT_BUDGET_FLOOR); + let mut produced: usize = 0; + items + .into_iter() + .map(|item| { + let result = encode_item(item, produced <= batch_budget); + if let Some(encoded) = &result.encoded { + produced = produced.saturating_add(encoded.len()); + } + result + }) + .collect() } -fn encode_item(item: ToonEncodeItem) -> ToonEncodeResult { - let normalized = if item.unwrap { - unwrap_tool_content(item.raw_content) - } else { - item.raw_content +/// The item outcome computed while the parsed DOM still borrows `raw_content`. +enum ItemOutcome { + /// Content was not parseable JSON (or was JSON but exceeded encode limits). + Encoded(Option), + /// The unwrap wrapper matched; the extracted text replaces `raw_content`. + Unwrapped(String), +} + +fn encode_item(item: ToonEncodeItem, encode_enabled: bool) -> ToonEncodeResult { + // Input size cap: see MAX_ITEM_INPUT_BYTES. Checked before any parse. + if item.raw_content.len() > MAX_ITEM_INPUT_BYTES { + return ToonEncodeResult { + normalized: item.raw_content, + encoded: None, + }; + } + // Single DOM parse per item: the unwrap check reuses the parsed value + // instead of parsing the content once to inspect the wrapper and a second + // time to encode. Only a matched wrapper needs the extra inner parse (its + // payload is a JSON string, not a subtree). The DOM borrows from + // `raw_content`, so the outcome is computed before `raw_content` moves. + // When the aggregate batch budget is exhausted (`encode_enabled` false), + // unwrap semantics are preserved but no encoding is produced. + let outcome = match json::parse_json(&item.raw_content) { + None => ItemOutcome::Encoded(None), + Some(value) => { + if item.unwrap { + match take_wrapper_text(value) { + Ok(text) => ItemOutcome::Unwrapped(text), + Err(value) => ItemOutcome::Encoded(encode_value( + &value, + item.raw_content.len(), + encode_enabled, + )), + } + } else { + ItemOutcome::Encoded(encode_value(&value, item.raw_content.len(), encode_enabled)) + } + } }; - let encoded = serde_json::from_str::(&normalized) - .ok() - .and_then(|value| toon_format::encode_default(&value).ok()); - ToonEncodeResult { - normalized, - encoded, + match outcome { + ItemOutcome::Encoded(encoded) => ToonEncodeResult { + normalized: item.raw_content, + encoded, + }, + ItemOutcome::Unwrapped(text) => { + let encoded = if encode_enabled { + json::parse_json(&text) + .and_then(|value| encode::encode_to_toon(&value, text.len()).ok()) + } else { + None + }; + ToonEncodeResult { + normalized: text, + encoded, + } + } + } +} + +fn encode_value(value: &JsonValue<'_>, input_len: usize, enabled: bool) -> Option { + if !enabled { + return None; } + encode::encode_to_toon(value, input_len).ok() } /// Port of `platform/backend/src/routes/proxy/utils/unwrap-tool-content.ts`: -/// if `content` parses as a JSON array whose FIRST element is -/// `{"type": "text", "text": , ...}`, return that text; otherwise return -/// `content` unchanged. First-element-only is deliberate (pinned TS behavior) — -/// extra wrapper elements are dropped from the encoding input. +/// if the parsed content is a JSON array whose FIRST element is +/// `{"type": "text", "text": , ...}`, return that text; otherwise give +/// the value back unchanged. First-element-only is deliberate (pinned TS +/// behavior) — extra wrapper elements are dropped from the encoding input. /// /// Divergence from JS `JSON.parse` (within the approved migration envelope): /// `serde_json` rejects escaped lone surrogates (e.g. `"\ud800"`) and @@ -67,17 +160,26 @@ fn encode_item(item: ToonEncodeItem) -> ToonEncodeResult { /// parses, so wrappers containing them are NOT unwrapped here — the content /// falls through unchanged and later fails to encode (`encoded: None`), i.e. /// the original payload is conservatively kept. -fn unwrap_tool_content(content: String) -> String { - let Ok(Value::Array(elements)) = serde_json::from_str(&content) else { - return content; +fn take_wrapper_text(value: JsonValue<'_>) -> Result> { + let JsonValue::Array(mut elements) = value else { + return Err(value); }; - let Some(Value::Object(mut first)) = elements.into_iter().next() else { - return content; + let text = match elements.first_mut() { + Some(JsonValue::Object(first)) + if first.get("type").and_then(JsonValue::as_str) == Some("text") => + { + match first.get_mut("text") { + // The wrapper array is discarded on this path, so taking the + // text out of it costs at most one copy (borrowed -> owned). + Some(JsonValue::String(text)) => Some(std::mem::take(text).into_owned()), + _ => None, + } + } + _ => None, }; - let is_text_block = first.get("type").and_then(Value::as_str) == Some("text"); - match (is_text_block, first.remove("text")) { - (true, Some(Value::String(text))) => text, - _ => content, + match text { + Some(text) => Ok(text), + None => Err(JsonValue::Array(elements)), } } @@ -251,6 +353,73 @@ mod tests { assert_eq!(huge.len(), "x: ".len() + 301); } + #[test] + fn aggregate_batch_budget_caps_total_retained_output() { + // Each item is ~301B of exponent-form numbers encoding to ~15.1KB — + // under its own per-item 16KiB floor, so 40 of them would retain + // ~600KB from ~12KB of input without the aggregate cap. Batch budget + // = 2 x 12040 + 16384 = 40464 bytes: items 0-2 encode (45315 bytes + // produced), everything after is skipped. + let raw = format!("[{}]", vec!["1e300"; 50].join(",")); + let items: Vec = (0..40) + .map(|i| ToonEncodeItem { + id: format!("i{i}"), + raw_content: raw.clone(), + unwrap: false, + }) + .collect(); + let total_input = raw.len() * 40; + let batch_budget = 2 * total_input + 16 * 1024; + + let results = toon_encode_tool_results(items); + assert_eq!(results.len(), 40); + let encoded_count = results.iter().filter(|r| r.encoded.is_some()).count(); + assert_eq!(encoded_count, 3, "first items encode, tail is skipped"); + assert!(results[0].encoded.is_some()); + assert!(results.last().expect("40 results").encoded.is_none()); + let produced: usize = results + .iter() + .filter_map(|r| r.encoded.as_ref().map(String::len)) + .sum(); + // Bounded: the budget plus at most one crossing item's output. + assert!( + produced <= batch_budget + 16 * 1024, + "retained output {produced} exceeds bound" + ); + assert!(results.iter().all(|r| r.normalized == raw)); + } + + #[test] + fn input_cap_skips_oversized_items_without_parsing() { + // Just over the cap: skipped entirely — no parse, no unwrap, content + // kept verbatim. + let over = format!(r#"["{}"]"#, "a".repeat(MAX_ITEM_INPUT_BYTES)); + assert!(over.len() > MAX_ITEM_INPUT_BYTES); + let result = encode_one(&over, true); + assert_eq!(result.normalized, over); + assert_eq!(result.encoded, None); + + // At the cap boundary: still parsed and encoded normally. + let under = format!(r#"["{}"]"#, "a".repeat(MAX_ITEM_INPUT_BYTES - 4)); + assert_eq!(under.len(), MAX_ITEM_INPUT_BYTES); + let result = encode_one(&under, true); + assert!(result.encoded.is_some()); + } + + #[test] + fn budget_exceeded_yields_none_and_keeps_original() { + // ~24KB of exponent-form numbers would expand ~60x past the 2x output + // budget; the item fails open: `encoded: None`, original payload kept. + // This pins the behavior change for adapters that apply compression + // unconditionally (Bedrock/MiniMax): a pathologically-expanding payload + // is now skipped instead of hugely inflated — closer to the old npm + // path, which emitted compact exponent forms. + let raw = format!("[{}]", vec!["1e300"; 4096].join(",")); + let result = encode_one(&raw, true); + assert_eq!(result.normalized, raw); + assert_eq!(result.encoded, None); + } + #[test] fn batch_is_positional_and_same_length() { let items = vec![ diff --git a/platform/archestra-rs/proxy-transform-core/tests/roundtrip_property.proptest-regressions b/platform/archestra-rs/proxy-transform-core/tests/roundtrip_property.proptest-regressions new file mode 100644 index 00000000000..6f90200da61 --- /dev/null +++ b/platform/archestra-rs/proxy-transform-core/tests/roundtrip_property.proptest-regressions @@ -0,0 +1,7 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc 664598e1d33a784d747ecd9467351725d66897eab37abbc66158e5cb22da4f8d # shrinks to value = String("0¡") diff --git a/platform/archestra-rs/proxy-transform-core/tests/roundtrip_property.rs b/platform/archestra-rs/proxy-transform-core/tests/roundtrip_property.rs index 656b6be14b8..7e4b6c404a9 100644 --- a/platform/archestra-rs/proxy-transform-core/tests/roundtrip_property.rs +++ b/platform/archestra-rs/proxy-transform-core/tests/roundtrip_property.rs @@ -24,7 +24,10 @@ //! `[1]{""}:` that the decoder rejects ("Field name cannot be empty"); //! - string content limits control/whitespace characters to `\n`, `\t` and //! space: exotic ones are emitted raw in unquoted positions and lost on -//! decode (e.g. a vertical tab in `"A\u{b} x"` decodes as `"A x"`). +//! decode (e.g. a vertical tab in `"A\u{b} x"` decodes as `"A x"`); +//! - a ROOT-level document that is a bare digit-leading string gets a space +//! inserted after the digit run by the decoder (`"0¡"` decodes as `"0 ¡"`); +//! container positions are unaffected, so only the root string is dodged. use proptest::prelude::*; use proxy_transform_core::{ToonEncodeItem, toon_encode_tool_results}; @@ -76,6 +79,18 @@ fn arb_json() -> impl Strategy { ] }) .prop_map(dodge_list_layout_quirk) + .prop_map(dodge_root_digit_string_quirk) +} + +/// See module docs: only a root-level digit-leading string trips the decoder's +/// digit-run split, so prefix it the same way the list-layout dodge does. +fn dodge_root_digit_string_quirk(value: Value) -> Value { + match value { + Value::String(text) if text.starts_with(|c: char| c.is_ascii_digit()) => { + Value::String(format!("_{text}")) + } + other => other, + } } /// Unconstrained JSON generator for encode-only properties: no decoder-quirk @@ -169,6 +184,46 @@ fn semantically_equal(a: &Value, b: &Value) -> bool { } proptest! { + /// Differential parity oracle: our encoder must produce byte-identical + /// output to `toon_format::encode_default` for any parseable JSON. Uses + /// the unconstrained generator — encode-only, so no decoder-quirk + /// exclusions apply. The allowed divergences are the deliberate + /// anti-amplification limits: when our encoder returns `None`, either the + /// input exceeds the per-item cap or the crate's output must genuinely + /// exceed `max(2 x input bytes, 16KiB)`. (The aggregate batch budget never + /// binds here: a single-item batch is gated before any output exists.) + #[test] + fn encoder_matches_toon_format_crate(value in arb_json_encode_only()) { + let raw = serde_json::to_string(&value).expect("serialize generated value"); + let parsed: Value = serde_json::from_str(&raw).expect("reparse generated document"); + let raw_len = raw.len(); + let budget = (2 * raw_len).max(16 * 1024); + let results = toon_encode_tool_results(vec![ToonEncodeItem { + id: "diff".to_string(), + raw_content: raw, + unwrap: false, + }]); + let expected = toon_format::encode_default(&parsed).ok(); + match (&results[0].encoded, &expected) { + (None, Some(crate_output)) => prop_assert!( + raw_len > proxy_transform_core::MAX_ITEM_INPUT_BYTES + || crate_output.len() > budget, + "our encoder returned None but no skip condition held (input {} bytes, \ + crate output {} bytes, budget {}) for value: {}", + raw_len, + crate_output.len(), + budget, + parsed + ), + (ours, expected) => prop_assert_eq!( + ours, + expected, + "encoder diverged from toon-format crate for value: {}", + parsed + ), + } + } + #[test] fn encode_decode_roundtrips(value in arb_json()) { let raw = serde_json::to_string(&value).expect("serialize generated value"); diff --git a/platform/archestra-rs/proxy-transform-rs/index.d.ts b/platform/archestra-rs/proxy-transform-rs/index.d.ts index ef755184a1b..0f155a7fd29 100644 --- a/platform/archestra-rs/proxy-transform-rs/index.d.ts +++ b/platform/archestra-rs/proxy-transform-rs/index.d.ts @@ -15,7 +15,9 @@ export interface ToonEncodeItem { * The transformation output for one item. `normalized` is the unwrapped string * when unwrapping was requested and matched, else the original `raw_content` * (adapters tokenize it for accounting). `encoded` is the TOON encoding, or - * `None` when the content is not parseable JSON. + * `None` when the content is not parseable JSON — or when encoding would + * exceed the anti-amplification output budget (see `encode` module docs); + * adapters keep the original payload either way. * * `use_nullable` makes `encoded: None` cross the boundary as an explicit JS * `null` (typed `string | null`) instead of an omitted key. diff --git a/platform/backend/src/routes/proxy/__bench__/compare-backends.ts b/platform/backend/src/routes/proxy/__bench__/compare-backends.ts deleted file mode 100644 index 71fba2364c2..00000000000 --- a/platform/backend/src/routes/proxy/__bench__/compare-backends.ts +++ /dev/null @@ -1,154 +0,0 @@ -/** - * One-off sanity script (not a permanent test): runs the TS reference backend - * and the native backend over the full benchmark corpora and classifies every - * output divergence. Expected: `normalized` byte-equal everywhere; `encoded` - * either byte-equal or a representation-only difference (both encodings decode - * to the same value via the npm decoder — e.g. the Rust v3 encoder quotes - * hyphenated scalars like `SKU-123` that npm 2.1.0 leaves bare, per the known - * npm→v3 migration delta established at T1). Anything that decodes differently - * is a real mismatch and fails the script. - * - * Run from platform/backend: - * pnpm exec tsx src/routes/proxy/__bench__/compare-backends.ts - */ -import "./bench-env"; -import assert from "node:assert/strict"; -import { decode as toonDecode } from "@toon-format/toon"; -import { buildBatch, buildJumboBatch, CORPUS_SPECS } from "./corpus"; -import { - encodeToolResultsReference, - type ToonKernelItem, -} from "./toon-kernel-reference"; - -const MAX_PRINTED_DIFFS = 10; - -type DivergenceKind = - | "normalized" // normalized strings differ (must never happen) - | "encodability" // one side encoded, the other returned null - | "representation" // byte-different encodings decoding to the same value - | "semantic"; // encodings decode to different values - -interface Divergence { - corpus: string; - index: number; - kind: DivergenceKind; - detail: string; -} - -async function main(): Promise { - const divergences: Divergence[] = []; - let total = 0; - let encodable = 0; - for (const spec of CORPUS_SPECS) { - const r = await compareCorpus(spec.name, buildBatch(spec, 42), divergences); - total += r.total; - encodable += r.encodable; - } - const jumbo = await compareCorpus("70MB", buildJumboBatch(4242), divergences); - total += jumbo.total; - encodable += jumbo.encodable; - - const byKind = new Map(); - for (const d of divergences) { - const bucket = byKind.get(d.kind) ?? []; - bucket.push(d); - byKind.set(d.kind, bucket); - } - console.info( - `compare-backends: ${total} items (${encodable} encodable), ${divergences.length} byte-divergence(s)`, - ); - for (const [kind, bucket] of byKind) { - console.info(` ${kind}: ${bucket.length}`); - for (const d of bucket.slice(0, MAX_PRINTED_DIFFS)) { - console.info(` ${d.corpus}[${d.index}] ${d.detail}`); - } - if (bucket.length > MAX_PRINTED_DIFFS) { - console.info(` ... ${bucket.length - MAX_PRINTED_DIFFS} more`); - } - } - - const broken = divergences.filter((d) => d.kind !== "representation"); - process.exitCode = broken.length === 0 ? 0 : 1; -} - -// ============================================================================= -// INTERNALS -// ============================================================================= - -async function compareCorpus( - name: string, - items: ToonKernelItem[], - divergences: Divergence[], -): Promise<{ total: number; encodable: number }> { - const { toonEncodeToolResults } = await import("../utils/toon-native"); - const tsResults = encodeToolResultsReference(items); - const nativeResults = await toonEncodeToolResults( - items.map(({ rawContent, unwrap }, i) => ({ - id: `cmp_${i}`, - rawContent, - unwrap, - })), - ); - if (nativeResults === null) { - throw new Error("native backend unavailable"); - } - let encodable = 0; - items.forEach((_, i) => { - const ts = tsResults[i]; - const native = nativeResults[i]; - if (ts.normalized !== native.normalized) { - divergences.push({ - corpus: name, - index: i, - kind: "normalized", - detail: firstDiff(ts.normalized, native.normalized), - }); - } - if (ts.encoded !== null || native.encoded !== null) { - encodable++; - } - if (ts.encoded === native.encoded) { - return; - } - if (ts.encoded === null || native.encoded === null) { - divergences.push({ - corpus: name, - index: i, - kind: "encodability", - detail: `ts=${ts.encoded === null ? "" : "encoded"} native=${ - native.encoded === null ? "" : "encoded" - }`, - }); - return; - } - divergences.push({ - corpus: name, - index: i, - kind: classifyEncodedDiff(ts.encoded, native.encoded), - detail: firstDiff(ts.encoded, native.encoded), - }); - }); - return { total: items.length, encodable }; -} - -function classifyEncodedDiff(ts: string, native: string): DivergenceKind { - try { - assert.deepEqual(toonDecode(native), toonDecode(ts)); - return "representation"; - } catch { - return "semantic"; - } -} - -function firstDiff(a: string, b: string): string { - const n = Math.min(a.length, b.length); - let i = 0; - while (i < n && a[i] === b[i]) { - i++; - } - const ctx = (s: string) => - JSON.stringify(s.slice(Math.max(0, i - 40), i + 40)); - return `at byte ${i}: ts=${ctx(a)} native=${ctx(b)}`; -} - -main(); diff --git a/platform/backend/src/routes/proxy/__bench__/toon-kernel-reference.ts b/platform/backend/src/routes/proxy/__bench__/toon-kernel-reference.ts deleted file mode 100644 index d1b85f19c95..00000000000 --- a/platform/backend/src/routes/proxy/__bench__/toon-kernel-reference.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { encode as toonEncode } from "@toon-format/toon"; -import { unwrapToolContent } from "../utils/unwrap-tool-content"; - -export interface ToonKernelItem { - rawContent: string; - unwrap: boolean; -} - -export interface ToonKernelResult { - normalized: string; - encoded: string | null; -} - -/** - * TS reference backend for the planned native kernel boundary: - * batch of { rawContent, unwrap } -> { normalized, encoded }. - * - * Mirrors the per-item pipeline of convertToolResultsToToon - * (../adapters/openai.ts:1261+): unwrapToolContent -> JSON.parse -> - * toonEncode, yielding `encoded: null` when the content is not parseable - * JSON (the adapter then keeps the original content). The double parse - * (inside unwrapToolContent and again here) is deliberate — it is what the - * production path pays today. Output equivalence with the real adapter path - * is checked by validate-reference.ts. - */ -export function encodeToolResultsReference( - items: ToonKernelItem[], -): ToonKernelResult[] { - return items.map(({ rawContent, unwrap }) => { - const normalized = unwrap ? unwrapToolContent(rawContent) : rawContent; - try { - const parsed = JSON.parse(normalized); - return { normalized, encoded: toonEncode(parsed) }; - } catch { - return { normalized, encoded: null }; - } - }); -} diff --git a/platform/backend/src/routes/proxy/__bench__/validate-reference.ts b/platform/backend/src/routes/proxy/__bench__/validate-reference.ts deleted file mode 100644 index 8b084d962d8..00000000000 --- a/platform/backend/src/routes/proxy/__bench__/validate-reference.ts +++ /dev/null @@ -1,128 +0,0 @@ -/** - * One-off assertion script (not a permanent test): validates that the bench - * harness's TS reference backend produces byte-identical transformed content - * to the real adapter path (convertToolResultsToToon in ../adapters/openai.ts) - * on a set of fixtures, including the tokenizer keep/reject decision. - * - * Run from platform/backend: - * pnpm exec tsx src/routes/proxy/__bench__/validate-reference.ts - */ -import "./bench-env"; -import assert from "node:assert/strict"; -import { ModelModel } from "@/models"; -import { getTokenizer } from "@/tokenizers"; -import type { OpenAi } from "@/types"; -import { convertToolResultsToToon } from "../adapters/openai"; -import { encodeToolResultsReference } from "./toon-kernel-reference"; - -// True process boundary (Postgres). Pricing lookup is irrelevant to the -// transformation output being validated, and this throwaway script runs -// without a database. -ModelModel.calculateCostSavings = async () => 0; - -const uniformArray = JSON.stringify( - Array.from({ length: 50 }, (_, i) => ({ - id: i, - name: `item ${i}`, - status: i % 2 === 0 ? "active" : "archived", - score: i * 1.5, - })), -); - -const FIXTURES: string[] = [ - // Uniform array of objects — TOON compression expected to win. - uniformArray, - // n8n/Vercel-style text-block wrapper around JSON. - JSON.stringify([{ type: "text", text: uniformArray }]), - // Multi-element wrapper — pins the first-text-element-only behavior. - JSON.stringify([ - { type: "text", text: uniformArray }, - { type: "text", text: '{"ignored":true}' }, - ]), - // Non-array JSON object root. - JSON.stringify({ - meta: { total: 3, source: "db" }, - rows: [ - { id: 1, value: "a" }, - { id: 2, value: "b" }, - { id: 3, value: "c" }, - ], - }), - // Non-JSON prose — adapter must keep it untouched. - "Command failed: ENOENT no such file or directory, open '/tmp/x'", - // Escaping / unicode / nesting / boundary-ish numbers. - JSON.stringify({ - text: 'line1\nline2\t"quoted" \\ back', - emoji: "héllo wörld ✓ 日本語", - nested: { deep: { deeper: [1, 2, { x: null }] } }, - numbers: [0, -0, 1e21, 9007199254740991, 0.1], - }), - // Tiny payload where compression may or may not win — decision replicated. - '{"a":1}', -]; - -async function main(): Promise { - const tokenizer = getTokenizer("openai"); - const messages: OpenAi.Types.ChatCompletionsRequest["messages"] = [ - { role: "user", content: "run the tools" }, - ...FIXTURES.map( - (content, i) => - ({ - role: "tool", - tool_call_id: `call_${i}`, - content, - }) as const, - ), - ]; - - const { messages: transformed, stats } = await convertToolResultsToToon( - messages, - "gpt-4o", - "openai", - ); - - let compressedCount = 0; - FIXTURES.forEach((raw, i) => { - const actual = transformed[i + 1]; - assert.equal(actual.role, "tool"); - - // Reference backend, then the adapter's tokenizer keep/reject decision - // replicated on top of it (openai.ts:1292-1305). - const [ref] = encodeToolResultsReference([ - { rawContent: raw, unwrap: true }, - ]); - let expected = raw; - if (ref.encoded !== null) { - const tokensBefore = tokenizer.countTokens([ - { role: "user", content: ref.normalized }, - ]); - const tokensAfter = tokenizer.countTokens([ - { role: "user", content: ref.encoded }, - ]); - if (tokensAfter < tokensBefore) { - expected = ref.encoded; - compressedCount++; - } - } - assert.equal( - actual.content, - expected, - `fixture ${i}: adapter output differs from reference backend`, - ); - }); - - // Guard against a vacuous pass: the encode path must actually fire. - assert.ok( - compressedCount >= 3, - `expected >=3 fixtures to compress, got ${compressedCount}`, - ); - assert.ok(stats.hadToolResults); - assert.ok(stats.wasEffective); - - console.info( - `validate-reference: OK (${FIXTURES.length} fixtures, ${compressedCount} compressed, ` + - `tokensBefore=${stats.tokensBefore}, tokensAfter=${stats.tokensAfter})`, - ); -} - -main(); diff --git a/platform/backend/src/routes/proxy/utils/unwrap-tool-content.test.ts b/platform/backend/src/routes/proxy/utils/unwrap-tool-content.test.ts deleted file mode 100644 index 6caf0238641..00000000000 --- a/platform/backend/src/routes/proxy/utils/unwrap-tool-content.test.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { describe, expect, test } from "@/test"; -import { unwrapToolContent } from "./unwrap-tool-content"; - -describe("unwrapToolContent", () => { - test("unwraps stringified array with text block wrapper", () => { - const wrapped = '[{"type":"text","text":"{\\"data\\":\\"value\\"}"}]'; - const result = unwrapToolContent(wrapped); - expect(result).toBe('{"data":"value"}'); - }); - - test("unwraps array object with text block wrapper", () => { - const wrapped = [{ type: "text", text: '{"data":"value"}' }]; - const result = unwrapToolContent(wrapped); - expect(result).toBe('{"data":"value"}'); - }); - - test("returns plain JSON string unchanged", () => { - const plain = '{"data":"value"}'; - const result = unwrapToolContent(plain); - expect(result).toBe('{"data":"value"}'); - }); - - test("returns non-JSON string unchanged", () => { - const plain = "just plain text"; - const result = unwrapToolContent(plain); - expect(result).toBe("just plain text"); - }); - - test("handles empty array", () => { - const empty = "[]"; - const result = unwrapToolContent(empty); - expect(result).toBe("[]"); - }); - - test("handles array without type:text", () => { - const noType = '[{"foo":"bar"}]'; - const result = unwrapToolContent(noType); - expect(result).toBe('[{"foo":"bar"}]'); - }); - - test("handles array with type but no text field", () => { - const noText = '[{"type":"text","content":"value"}]'; - const result = unwrapToolContent(noText); - expect(result).toBe('[{"type":"text","content":"value"}]'); - }); - - test("unwraps complex nested JSON", () => { - const wrapped = - '[{"type":"text","text":"{\\"issues\\":[{\\"id\\":123,\\"title\\":\\"Test\\"}]}"}]'; - const result = unwrapToolContent(wrapped); - expect(result).toBe('{"issues":[{"id":123,"title":"Test"}]}'); - }); - - test("handles object input (non-string)", () => { - const obj = { data: "value" }; - const result = unwrapToolContent(obj); - expect(result).toBe('{"data":"value"}'); - }); - - test("unwraps when given as object array", () => { - const wrapped = [{ type: "text", text: '{"issues":[{"id":123}]}' }]; - const result = unwrapToolContent(wrapped); - expect(result).toBe('{"issues":[{"id":123}]}'); - }); - - test("returns unwrapped content from multiple wrapper formats", () => { - // String format - const stringWrapped = - '[{"type":"text","text":"{\\"temperature\\":20,\\"condition\\":\\"sunny\\"}"}]'; - expect(unwrapToolContent(stringWrapped)).toBe( - '{"temperature":20,"condition":"sunny"}', - ); - - // Array format - const arrayWrapped = [ - { type: "text", text: '{"temperature":20,"condition":"sunny"}' }, - ]; - expect(unwrapToolContent(arrayWrapped)).toBe( - '{"temperature":20,"condition":"sunny"}', - ); - }); -}); diff --git a/platform/backend/src/routes/proxy/utils/unwrap-tool-content.ts b/platform/backend/src/routes/proxy/utils/unwrap-tool-content.ts deleted file mode 100644 index 64d7b1fd349..00000000000 --- a/platform/backend/src/routes/proxy/utils/unwrap-tool-content.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Unwrap extra text block wrapping from tool result content. - * - * Some clients (like n8n, Vercel AI SDK) wrap tool results in a text block structure: - * - Input: "[{\"type\":\"text\",\"text\":\"{\\\"data\\\":...}\"}]" - * - Output: "{\"data\":...}" - * - * Or as an array: - * - Input: [{"type":"text","text":"{\"data\":...}"}] - * - Output: "{\"data\":...}" - * - * This is necessary for TOON conversion which expects the raw JSON string, - * not wrapped in additional structures. - */ -export function unwrapToolContent(content: string | unknown): string { - // Convert to string if it's not already - const contentStr = - typeof content === "string" ? content : JSON.stringify(content); - - try { - const parsed = JSON.parse(contentStr); - - // Check for wrapper format: [{"type":"text","text":"..."}] - if ( - Array.isArray(parsed) && - parsed.length > 0 && - parsed[0]?.type === "text" && - typeof parsed[0]?.text === "string" - ) { - // Return the unwrapped text content - return parsed[0].text; - } - - // Not wrapped, return as-is - return contentStr; - } catch { - // Not valid JSON, return as-is - return contentStr; - } -} From 4b5a87da8bd0ef819f3c45cfd808e89e28c74f47 Mon Sep 17 00:00:00 2001 From: Arseny Kravchenko Date: Fri, 10 Jul 2026 17:14:15 +0200 Subject: [PATCH 09/18] chore(proxy): drop @toon-format/toon; docs for native TOON pipeline Backend no longer depends on the npm TOON implementation: the bench harness is native-only, unwrap-tool-content.ts is deleted (the Rust core owns unwrapping). Docs: provider-authoring guide points at the shared toon-native helper, costs-and-limits documents the addon_unavailable skip reason. Claude-Session: https://claude.ai/code/session_01QkeLJprfeq75d2NY39aFiu --- docs/pages/platform-adding-llm-providers.md | 8 +- docs/pages/platform-costs-and-limits.md | 3 +- platform/backend/package.json | 1 - .../proxy/__bench__/bench-concurrency.ts | 20 ++--- .../proxy/__bench__/bench-toon-kernel.ts | 29 +++---- .../src/routes/proxy/__bench__/corpus.ts | 4 +- .../routes/proxy/__bench__/toon-backend.ts | 82 +++++++------------ platform/pnpm-lock.yaml | 8 -- 8 files changed, 57 insertions(+), 98 deletions(-) diff --git a/docs/pages/platform-adding-llm-providers.md b/docs/pages/platform-adding-llm-providers.md index 65d10d6550b..df141f223ac 100644 --- a/docs/pages/platform-adding-llm-providers.md +++ b/docs/pages/platform-adding-llm-providers.md @@ -3,7 +3,7 @@ title: Adding LLM Providers category: Development order: 2 description: Developer guide for implementing new LLM provider support in Archestra Platform -lastUpdated: 2026-04-29 +lastUpdated: 2026-07-10 --- @@ -171,10 +171,12 @@ The function must: 1. Iterate through provider-specific message array structure 2. Find tool result messages (e.g., `role: "tool"` in OpenAI, `tool_result` blocks in Anthropic, `functionResponse` parts in Gemini) -3. Parse JSON content and convert to TOON format using `@toon-format/toon` -4. Calculate token savings using the appropriate tokenizer +3. Encode the extracted results in one batch with the shared helper `toonEncodeToolResults()` from `backend/src/routes/proxy/utils/toon-native.ts` — it runs unwrap, JSON parse, and TOON encode in the native `@archestra/proxy-transform-rs` addon. Do not encode TOON in the adapter itself +4. Decide keep or reject per result in the adapter: count tokens with the provider tokenizer and keep the TOON version only when it saves tokens 5. Return compressed messages and compression statistics +The helper fails open. When the native addon is unavailable it returns `null`; the adapter must then keep the original tool results and report the `addon_unavailable` skip reason. + ### Metrics > **Note:** This is a known abstraction leak that we're planning to address in future versions. Thanks for bearing with us! diff --git a/docs/pages/platform-costs-and-limits.md b/docs/pages/platform-costs-and-limits.md index 9c11b5a6a7e..9e3f0514e73 100644 --- a/docs/pages/platform-costs-and-limits.md +++ b/docs/pages/platform-costs-and-limits.md @@ -2,7 +2,7 @@ title: Costs & Limits category: LLM Proxy order: 4 -lastUpdated: 2026-06-22 +lastUpdated: 2026-07-10 --- @@ -99,6 +99,7 @@ Compression is skipped when: - TOON is disabled - a response has no tool results - the TOON version would not save tokens +- the compression engine is unavailable (`addon_unavailable`) — an infrastructure failure; the request proceeds with uncompressed tool results Archestra records before/after token counts and savings when compression is applied, so those savings appear in logs and aggregate cost reporting. diff --git a/platform/backend/package.json b/platform/backend/package.json index 87d0811653e..2a598a8835b 100644 --- a/platform/backend/package.json +++ b/platform/backend/package.json @@ -115,7 +115,6 @@ "@smithy/eventstream-codec": "^4.2.11", "@smithy/signature-v4": "^5.3.11", "@smithy/util-utf8": "^4.2.2", - "@toon-format/toon": "^2.1.0", "@types/pdf-parse": "^1.1.5", "ai": "catalog:", "asana": "3.1.11", diff --git a/platform/backend/src/routes/proxy/__bench__/bench-concurrency.ts b/platform/backend/src/routes/proxy/__bench__/bench-concurrency.ts index d8746ba334d..86469d005f9 100644 --- a/platform/backend/src/routes/proxy/__bench__/bench-concurrency.ts +++ b/platform/backend/src/routes/proxy/__bench__/bench-concurrency.ts @@ -3,19 +3,17 @@ * * Runs 8 concurrent async batches of the TOON kernel (mixed 1KB-5MB items, * ~8.8MB per batch, ~70MB total) with an event-loop yield between items, - * and reports p50/p99/max event-loop delay plus peak RSS. Backend selectable - * via BENCH_BACKEND=ts|native (see toon-backend.ts). + * and reports p50/p99/max event-loop delay plus peak RSS, through the native + * backend (see toon-backend.ts). * * Run from platform/backend: * pnpm exec tsx src/routes/proxy/__bench__/bench-concurrency.ts - * BENCH_BACKEND=native pnpm exec tsx src/routes/proxy/__bench__/bench-concurrency.ts */ import { monitorEventLoopDelay, performance } from "node:perf_hooks"; import { setImmediate as yieldEventLoop } from "node:timers/promises"; import { fmt } from "./bench-util"; import { batchBytes, buildBatch, type CorpusSpec } from "./corpus"; -import { resolveToonBackend, type ToonBenchBackend } from "./toon-backend"; -import type { ToonKernelItem } from "./toon-kernel-reference"; +import { encodeBatchNative, type ToonKernelItem } from "./toon-backend"; const CONCURRENCY = 8; @@ -42,12 +40,9 @@ function sampleRss(): void { } } -async function worker( - backend: ToonBenchBackend, - items: ToonKernelItem[], -): Promise { +async function worker(items: ToonKernelItem[]): Promise { for (const item of items) { - const [result] = await backend.encode([item]); + const [result] = await encodeBatchNative([item]); sink += result.encoded === null ? 0 : result.encoded.length; sampleRss(); await yieldEventLoop(); @@ -55,7 +50,6 @@ async function worker( } async function main(): Promise { - const backend = await resolveToonBackend(); const batches: ToonKernelItem[][] = []; for (let i = 0; i < CONCURRENCY; i++) { batches.push(buildWorkerBatch(1000 + i)); @@ -68,7 +62,7 @@ async function main(): Promise { const rssTimer = setInterval(sampleRss, 25); histogram.enable(); const start = performance.now(); - await Promise.all(batches.map((b) => worker(backend, b))); + await Promise.all(batches.map((b) => worker(b))); const wallMs = performance.now() - start; histogram.disable(); clearInterval(rssTimer); @@ -76,7 +70,7 @@ async function main(): Promise { const toMs = (ns: number) => ns / 1e6; console.info( - `bench-concurrency: 8 concurrent TOON kernel batches (${backend.name} backend)`, + "bench-concurrency: 8 concurrent TOON kernel batches (native backend)", ); console.info( [ diff --git a/platform/backend/src/routes/proxy/__bench__/bench-toon-kernel.ts b/platform/backend/src/routes/proxy/__bench__/bench-toon-kernel.ts index dfd7337a433..befec271e8e 100644 --- a/platform/backend/src/routes/proxy/__bench__/bench-toon-kernel.ts +++ b/platform/backend/src/routes/proxy/__bench__/bench-toon-kernel.ts @@ -1,11 +1,10 @@ /** - * Benchmark (a): TOON kernel (unwrap -> JSON.parse -> toonEncode) over - * deterministic synthetic corpora, against a selectable backend - * (BENCH_BACKEND=ts|native — see toon-backend.ts). + * Benchmark (a): TOON kernel (unwrap -> JSON.parse -> TOON encode) over + * deterministic synthetic corpora, through the native backend + * (see toon-backend.ts). * * Run from platform/backend: * pnpm exec tsx src/routes/proxy/__bench__/bench-toon-kernel.ts - * BENCH_BACKEND=native pnpm exec tsx src/routes/proxy/__bench__/bench-toon-kernel.ts */ import { performance } from "node:perf_hooks"; import { fmt, summarize } from "./bench-util"; @@ -15,8 +14,7 @@ import { buildJumboBatch, CORPUS_SPECS, } from "./corpus"; -import { resolveToonBackend, type ToonBenchBackend } from "./toon-backend"; -import type { ToonKernelItem } from "./toon-kernel-reference"; +import { encodeBatchNative, type ToonKernelItem } from "./toon-backend"; const TIME_BUDGET_MS = 4_000; const MIN_ITERATIONS = 5; @@ -25,12 +23,9 @@ const MAX_ITERATIONS = 200; // Prevents dead-code elimination of the encode results. let sink = 0; -async function runBatch( - backend: ToonBenchBackend, - items: ToonKernelItem[], -): Promise { +async function runBatch(items: ToonKernelItem[]): Promise { const start = performance.now(); - const results = await backend.encode(items); + const results = await encodeBatchNative(items); const elapsed = performance.now() - start; for (const r of results) { sink += r.encoded === null ? r.normalized.length : r.encoded.length; @@ -39,12 +34,11 @@ async function runBatch( } async function benchCorpus( - backend: ToonBenchBackend, name: string, items: ToonKernelItem[], ): Promise { const totalMB = batchBytes(items) / (1 << 20); - await runBatch(backend, items); // warmup + await runBatch(items); // warmup const samples: number[] = []; const budgetStart = performance.now(); while ( @@ -52,7 +46,7 @@ async function benchCorpus( (samples.length < MIN_ITERATIONS || performance.now() - budgetStart < TIME_BUDGET_MS) ) { - samples.push(await runBatch(backend, items)); + samples.push(await runBatch(items)); } const s = summarize(samples); const mbPerSec = totalMB / (s.meanMs / 1000); @@ -72,12 +66,11 @@ async function benchCorpus( } async function main(): Promise { - const backend = await resolveToonBackend(); - console.info(`bench-toon-kernel: ${backend.name} backend`); + console.info("bench-toon-kernel: native backend"); for (const spec of CORPUS_SPECS) { - await benchCorpus(backend, spec.name, buildBatch(spec, 42)); + await benchCorpus(spec.name, buildBatch(spec, 42)); } - await benchCorpus(backend, "70MB", buildJumboBatch(4242)); + await benchCorpus("70MB", buildJumboBatch(4242)); console.info(`(sink=${sink})`); } diff --git a/platform/backend/src/routes/proxy/__bench__/corpus.ts b/platform/backend/src/routes/proxy/__bench__/corpus.ts index 71d37f67b1d..5a77d3a2ebb 100644 --- a/platform/backend/src/routes/proxy/__bench__/corpus.ts +++ b/platform/backend/src/routes/proxy/__bench__/corpus.ts @@ -1,4 +1,4 @@ -import type { ToonKernelItem } from "./toon-kernel-reference"; +import type { ToonKernelItem } from "./toon-backend"; /** * Deterministic synthetic corpora for the TOON kernel benchmarks (T0). @@ -6,7 +6,7 @@ import type { ToonKernelItem } from "./toon-kernel-reference"; * Every batch is fully reproducible from its seed. Item mix per 10 items: * 6 uniform JSON arrays of objects, 2 non-array JSON objects, 2 non-JSON * prose strings; 2 of the JSON items are wrapped in the n8n/Vercel-style - * `[{"type":"text","text":...}]` wrapper (exercising unwrapToolContent), + * `[{"type":"text","text":...}]` wrapper (exercising the unwrap path), * and roughly 1 in 7 unwrapped items uses `unwrap: false` (Bedrock-style). */ diff --git a/platform/backend/src/routes/proxy/__bench__/toon-backend.ts b/platform/backend/src/routes/proxy/__bench__/toon-backend.ts index 421d3f386b5..d25bd4e099f 100644 --- a/platform/backend/src/routes/proxy/__bench__/toon-backend.ts +++ b/platform/backend/src/routes/proxy/__bench__/toon-backend.ts @@ -1,61 +1,39 @@ /** - * Benchmark backend selection for the TOON kernel harness (T0/T8): - * BENCH_BACKEND=ts (default) TS reference implementation - * BENCH_BACKEND=native the real production helper (utils/toon-native.ts) - * over the Rust addon, so JS→Rust string copies, async - * scheduling, and result conversion are all inside the - * measurement. + * TOON kernel backend for the benchmark harness (T0/T8): the real production + * helper (utils/toon-native.ts) over the Rust addon, so JS→Rust string + * copies, async scheduling, and result conversion are all inside the + * measurement. Boundary: batch of { rawContent, unwrap } → results. * - * Both backends share the "batch of { rawContent, unwrap } → results" - * boundary, corpora, and stats output — the pre-registered threshold compares - * exactly these two numbers. + * The TS reference backend this was originally compared against (npm + * @toon-format/toon) was removed once all adapters cut over to the native + * kernel — baseline numbers are recorded in the PR; git history keeps the + * code. */ import "./bench-env"; -import { - encodeToolResultsReference, - type ToonKernelItem, - type ToonKernelResult, -} from "./toon-kernel-reference"; +import { toonEncodeToolResults } from "../utils/toon-native"; -export interface ToonBenchBackend { - name: "ts" | "native"; - encode: (items: ToonKernelItem[]) => Promise; +export interface ToonKernelItem { + rawContent: string; + unwrap: boolean; } -export async function resolveToonBackend(): Promise { - const requested = process.env.BENCH_BACKEND ?? "ts"; - switch (requested) { - case "ts": - return { - name: "ts", - encode: async (items) => encodeToolResultsReference(items), - }; - case "native": { - // Dynamic import keeps the backend module graph (logging, metrics, - // config) out of TS-backend runs. - const { toonEncodeToolResults } = await import("../utils/toon-native"); - return { - name: "native", - encode: async (items) => { - const results = await toonEncodeToolResults( - items.map(({ rawContent, unwrap }, i) => ({ - id: `bench_${i}`, - rawContent, - unwrap, - })), - ); - if (results === null) { - throw new Error( - "native TOON backend unavailable (addon failed to load)", - ); - } - return results; - }, - }; - } - default: - throw new Error( - `unknown BENCH_BACKEND "${requested}" (expected "ts" or "native")`, - ); +export interface ToonKernelResult { + normalized: string; + encoded: string | null; +} + +export async function encodeBatchNative( + items: ToonKernelItem[], +): Promise { + const results = await toonEncodeToolResults( + items.map(({ rawContent, unwrap }, i) => ({ + id: `bench_${i}`, + rawContent, + unwrap, + })), + ); + if (results === null) { + throw new Error("native TOON backend unavailable (addon failed to load)"); } + return results; } diff --git a/platform/pnpm-lock.yaml b/platform/pnpm-lock.yaml index 48c45a818ce..a1c73c01433 100644 --- a/platform/pnpm-lock.yaml +++ b/platform/pnpm-lock.yaml @@ -361,9 +361,6 @@ importers: '@smithy/util-utf8': specifier: ^4.2.2 version: 4.2.2 - '@toon-format/toon': - specifier: ^2.1.0 - version: 2.1.0 '@types/pdf-parse': specifier: ^1.1.5 version: 1.1.5 @@ -5868,9 +5865,6 @@ packages: '@tokenizer/token@0.3.0': resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} - '@toon-format/toon@2.1.0': - resolution: {integrity: sha512-JwWptdF5eOA0HaQxbKAzkpQtR4wSWTEfDlEy/y3/4okmOAX1qwnpLZMmtEWr+ncAhTTY1raCKH0kteHhSXnQqg==} - '@turbo/darwin-64@2.9.14': resolution: {integrity: sha512-t7QiPflaEyBE4oayeZtSmu4mEfjgIrcNlNNl1z1dmIVPqEdtA7+CfTf8d7KXsOGPh6aNgWjKxyvQg9uGfDQF+A==} cpu: [x64] @@ -16569,8 +16563,6 @@ snapshots: '@tokenizer/token@0.3.0': {} - '@toon-format/toon@2.1.0': {} - '@turbo/darwin-64@2.9.14': optional: true From c83fbf24734795d195d0a8cd015ccce094e6d95e Mon Sep 17 00:00:00 2001 From: Arseny Kravchenko Date: Fri, 10 Jul 2026 17:49:32 +0200 Subject: [PATCH 10/18] fix(archestra-rs): saturating batch-input sum in aggregate TOON budget Aligns the sum with the saturating arithmetic already used around it; wrap was unreachable on supported 64-bit targets but inconsistent. Claude-Session: https://claude.ai/code/session_01QkeLJprfeq75d2NY39aFiu --- platform/archestra-rs/proxy-transform-core/src/lib.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/platform/archestra-rs/proxy-transform-core/src/lib.rs b/platform/archestra-rs/proxy-transform-core/src/lib.rs index f87176fae06..659ac463705 100644 --- a/platform/archestra-rs/proxy-transform-core/src/lib.rs +++ b/platform/archestra-rs/proxy-transform-core/src/lib.rs @@ -64,7 +64,10 @@ pub const MAX_ITEM_INPUT_BYTES: usize = 10 * 1024 * 1024; /// once the running total exceeds it, remaining items are not encoded /// (`encoded: None`, fail-open like the per-item budget, unwrap still applied). pub fn toon_encode_tool_results(items: Vec) -> Vec { - let total_input: usize = items.iter().map(|item| item.raw_content.len()).sum(); + let total_input: usize = items + .iter() + .map(|item| item.raw_content.len()) + .fold(0usize, usize::saturating_add); let batch_budget = total_input .saturating_mul(2) .saturating_add(encode::OUTPUT_BUDGET_FLOOR); From 2eee55f20498f0ed8561ffd2111bae0dbe2aca80 Mon Sep 17 00:00:00 2001 From: Arseny Kravchenko Date: Fri, 10 Jul 2026 19:07:01 +0200 Subject: [PATCH 11/18] ci(proxy-transform-rs): drop clippy from check:ci to match sibling crates The Platform Lint job's toolchain does not install the clippy component; clippy already runs workspace-wide in Platform Rust Checks. Claude-Session: https://claude.ai/code/session_01QkeLJprfeq75d2NY39aFiu --- platform/archestra-rs/proxy-transform-rs/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform/archestra-rs/proxy-transform-rs/package.json b/platform/archestra-rs/proxy-transform-rs/package.json index ca325e2166e..71f97291da5 100644 --- a/platform/archestra-rs/proxy-transform-rs/package.json +++ b/platform/archestra-rs/proxy-transform-rs/package.json @@ -8,7 +8,7 @@ "scripts": { "build": "napi build --release --platform", "build:dev": "napi build --profile release-fast --platform", - "check:ci": "cargo fmt --check --all && cargo check -p proxy_transform_core -p proxy_transform_rs --locked && cargo clippy -p proxy_transform_core -p proxy_transform_rs --all-targets --locked -- -D warnings && cargo test -p proxy_transform_core --locked && cargo test -p proxy_transform_core --features napi --locked && pnpm build && pnpm smoke && pnpm smoke:esm", + "check:ci": "cargo fmt --check --all && cargo check -p proxy_transform_core -p proxy_transform_rs --locked && cargo test -p proxy_transform_core --locked && cargo test -p proxy_transform_core --features napi --locked && pnpm build && pnpm smoke && pnpm smoke:esm", "check:musl": "cargo test -p proxy_transform_core --locked && pnpm build && pnpm smoke && pnpm smoke:esm", "lint": "cargo fmt --check --all", "smoke": "node smoke.test.cjs", From 955c6d2650bedf161aace184e96224e04869a29b Mon Sep 17 00:00:00 2001 From: Arseny Kravchenko Date: Fri, 10 Jul 2026 19:34:30 +0200 Subject: [PATCH 12/18] test(proxy): deduplicate the per-adapter TOON golden suites Extract the copy-pasted prelude (corpus loading, addon skip/fail gate, deterministic pricing, per-provider token counting) into a shared test harness, and drop the accounting-matrix rows that re-asserted exactly what each suite's full-request equality test already pins. Kept per suite: the full-request and interleaving tests, and the accounting cases a combined request cannot isolate (hadToolResults on uncounted-only input, lone-rejected/lone-larger wasEffective=false, Cohere wins-only, Bedrock branch rules). Claude-Session: https://claude.ai/code/session_01QkeLJprfeq75d2NY39aFiu --- .../anthropic-toon-compression.test.ts | 208 ++++----- .../adapters/bedrock-toon-compression.test.ts | 412 +++++++----------- .../adapters/cohere-toon-compression.test.ts | 172 ++------ .../adapters/gemini-toon-compression.test.ts | 210 ++++----- .../adapters/minimax-toon-compression.test.ts | 202 +++------ .../adapters/openai-toon-compression.test.ts | 199 +++------ .../adapters/zhipuai-toon-compression.test.ts | 199 +++------ platform/backend/src/test/toon-golden.ts | 80 ++++ 8 files changed, 662 insertions(+), 1020 deletions(-) create mode 100644 platform/backend/src/test/toon-golden.ts diff --git a/platform/backend/src/routes/proxy/adapters/anthropic-toon-compression.test.ts b/platform/backend/src/routes/proxy/adapters/anthropic-toon-compression.test.ts index 551179237a6..e8ba0e047f1 100644 --- a/platform/backend/src/routes/proxy/adapters/anthropic-toon-compression.test.ts +++ b/platform/backend/src/routes/proxy/adapters/anthropic-toon-compression.test.ts @@ -1,43 +1,26 @@ // Pins the Anthropic adapter's TOON compression cutover to the native addon: // full transformed-request exact equality (TOON content from the committed v3 -// golden corpus, everything else byte-equal) and the exact -// ToolCompressionStats accounting matrix. Anthropic-specific semantics pinned -// here: candidates come from BOTH tool_result content shapes (string content -// and every text sub-block of array content — several blocks can share one -// tool_use_id), each text block is counted individually, rejected payloads -// count their original tokens in both totals, and hadToolResults reflects -// every non-error tool_result block (even unparseable ones). Requires the -// built addon: mandatory in CI; locally it skips visibly — run +// golden corpus, everything else byte-equal). Anthropic-specific semantics +// pinned here: candidates come from BOTH tool_result content shapes (string +// content and every text sub-block of array content — several blocks can +// share one tool_use_id), each text block is counted individually, rejected +// payloads count their original tokens in both totals, and hadToolResults +// reflects every non-error tool_result block (even unparseable ones). +// Requires the built addon: mandatory in CI; locally it skips visibly — run // `pnpm test:native` from platform/backend. -import { readFileSync } from "node:fs"; -import path from "node:path"; -import { ModelModel } from "@/models"; -import { describe, expect, test } from "@/test"; -import { getTokenizer } from "@/tokenizers"; +import { expect, test } from "@/test"; +import { + corpusEntry, + describeNative, + makeCountTokens, + upsertOneDollarPerTokenPricing, +} from "@/test/toon-golden"; import type { Anthropic } from "@/types"; import { anthropicAdapterFactory } from "./anthropic"; type AnthropicRequest = Anthropic.Types.MessagesRequest; -type GoldenEntry = { - name: string; - rawContent: string; - unwrap: boolean; - expected: { normalized: string; encoded: string | null }; -}; - -const CORPUS_PATH = path.resolve( - import.meta.dirname, - "../../../../../archestra-rs/proxy-transform-core/tests/fixtures/golden-corpus.json", -); -const corpus: GoldenEntry[] = JSON.parse(readFileSync(CORPUS_PATH, "utf8")); -function corpusEntry(name: string): GoldenEntry { - const entry = corpus.find((candidate) => candidate.name === name); - if (!entry) throw new Error(`golden corpus entry not found: ${name}`); - return entry; -} - // Corpus picks: a uniform array (compression wins), a wrapped // [{type:"text",...}] payload (unwrapped, compression wins), malformed JSON // (kept as-is), and a near-boundary object whose TOON encoding does not save @@ -47,46 +30,16 @@ const WRAPPED = corpusEntry("wrapped-single-text"); const MALFORMED = corpusEntry("malformed-prose"); const NEAR_BOUNDARY = corpusEntry("boundary-obj-1"); -const tokenizer = getTokenizer("anthropic"); -const countTokens = (content: string) => - tokenizer.countTokens([{ role: "user", content }]); - -// $1,000,000 per million input tokens = $1 per token, so expected costSavings -// equals tokens saved exactly. -async function upsertOneDollarPerTokenPricing() { - await ModelModel.upsert({ - externalId: "anthropic/claude-sonnet-4-5", +const countTokens = makeCountTokens("anthropic"); +const upsertPricing = () => + upsertOneDollarPerTokenPricing({ provider: "anthropic", modelId: "claude-sonnet-4-5", - inputModalities: null, - outputModalities: null, - customPricePerMillionInput: "1000000.00", - customPricePerMillionOutput: "1000000.00", - lastSyncedAt: new Date(), }); -} - -const addonLoadError: unknown = await import( - "@archestra/proxy-transform-rs" -).then( - () => null, - (error) => error, -); - -// In CI the suite must FAIL (never skip) when the addon is missing. -const describeNative = - addonLoadError === null || process.env.CI ? describe : describe.skip; -if (addonLoadError !== null && !process.env.CI) { - console.warn( - `[anthropic-toon-compression.test] skipping: @archestra/proxy-transform-rs is not built (${String( - addonLoadError, - )}). Run \`pnpm test:native\` from platform/backend.`, - ); -} describeNative("Anthropic adapter TOON compression (native addon)", () => { test("transforms the full provider request exactly (goldens for TOON, byte-equal elsewhere)", async () => { - await upsertOneDollarPerTokenPricing(); + await upsertPricing(); const makeRequest = (): AnthropicRequest => ({ model: "claude-sonnet-4-5", @@ -184,7 +137,7 @@ describeNative("Anthropic adapter TOON compression (native addon)", () => { }); test("compresses every text block of a multi-block tool_result sharing one tool_use_id", async () => { - await upsertOneDollarPerTokenPricing(); + await upsertPricing(); // One tool_result carries three text blocks under a single tool_use_id: // positional (locator-based) application must compress the first and @@ -246,7 +199,7 @@ describeNative("Anthropic adapter TOON compression (native addon)", () => { }); test("applies native results to the right blocks when non-candidate blocks are interleaved", async () => { - await upsertOneDollarPerTokenPricing(); + await upsertPricing(); // Non-candidates between candidates: a plain text block, an is_error // tool_result (whose content WOULD compress if wrongly collected), and a @@ -324,82 +277,75 @@ describeNative("Anthropic adapter TOON compression (native addon)", () => { }); }); - describe("exact ToolCompressionStats accounting matrix", () => { - // Unlike the OpenAI family, hadToolResults is true even for malformed - // content: Anthropic counts every non-error tool_result block it sees. - const rows: { - row: string; - entry: GoldenEntry; - compressed: boolean; - counted: boolean; - }[] = [ - { row: "malformed", entry: MALFORMED, compressed: false, counted: false }, - { - row: "ineffective (rejected: original counted in both totals)", - entry: NEAR_BOUNDARY, - compressed: false, - counted: true, - }, - { row: "effective", entry: UNIFORM, compressed: true, counted: true }, - { - row: "wrapped-array", - entry: WRAPPED, - compressed: true, - counted: true, - }, - ]; + // The case the combined request cannot isolate: unlike the OpenAI family, + // hadToolResults is true even when the only non-error tool_result block is + // malformed — Anthropic counts every block it sees. + test("a lone malformed result: zeroed totals but hadToolResults=true, message untouched", async () => { + await upsertPricing(); - for (const { row, entry, compressed, counted } of rows) { - test(row, async () => { - await upsertOneDollarPerTokenPricing(); - - const adapter = anthropicAdapterFactory.createRequestAdapter({ - model: "claude-sonnet-4-5", - max_tokens: 1024, - messages: [ - { role: "user", content: "run the tool" }, + const makeRequest = (): AnthropicRequest => ({ + model: "claude-sonnet-4-5", + max_tokens: 1024, + messages: [ + { role: "user", content: "run the tool" }, + { + role: "user", + content: [ { - role: "user", - content: [ - { - type: "tool_result", - tool_use_id: "toolu_1", - content: entry.rawContent, - }, - ], + type: "tool_result", + tool_use_id: "toolu_1", + content: MALFORMED.rawContent, }, ], - }); - const stats = await adapter.applyToonCompression("claude-sonnet-4-5"); + }, + ], + }); - const tokensBefore = counted - ? countTokens(entry.expected.normalized) - : 0; - const tokensAfter = compressed - ? countTokens(entry.expected.encoded as string) - : tokensBefore; - expect(stats).toStrictEqual({ - tokensBefore, - tokensAfter, - costSavings: tokensBefore - tokensAfter, - wasEffective: compressed, - hadToolResults: true, - }); + const adapter = anthropicAdapterFactory.createRequestAdapter(makeRequest()); + const stats = await adapter.applyToonCompression("claude-sonnet-4-5"); + + expect(stats).toStrictEqual({ + tokensBefore: 0, + tokensAfter: 0, + costSavings: 0, + wasEffective: false, + hadToolResults: true, + }); + expect(adapter.toProviderRequest()).toStrictEqual(makeRequest()); + }); - const [, toolMessage] = adapter.getProviderMessages(); - expect(toolMessage).toStrictEqual({ + test("a lone rejected result: original counted in both totals, wasEffective=false", async () => { + await upsertPricing(); + + const makeRequest = (): AnthropicRequest => ({ + model: "claude-sonnet-4-5", + max_tokens: 1024, + messages: [ + { role: "user", content: "run the tool" }, + { role: "user", content: [ { type: "tool_result", tool_use_id: "toolu_1", - content: compressed - ? (entry.expected.encoded as string) - : entry.rawContent, + content: NEAR_BOUNDARY.rawContent, }, ], - }); - }); - } + }, + ], + }); + + const adapter = anthropicAdapterFactory.createRequestAdapter(makeRequest()); + const stats = await adapter.applyToonCompression("claude-sonnet-4-5"); + + const boundaryTokens = countTokens(NEAR_BOUNDARY.expected.normalized); + expect(stats).toStrictEqual({ + tokensBefore: boundaryTokens, + tokensAfter: boundaryTokens, + costSavings: 0, + wasEffective: false, + hadToolResults: true, + }); + expect(adapter.toProviderRequest()).toStrictEqual(makeRequest()); }); }); diff --git a/platform/backend/src/routes/proxy/adapters/bedrock-toon-compression.test.ts b/platform/backend/src/routes/proxy/adapters/bedrock-toon-compression.test.ts index f88f4d59abc..60510cf833e 100644 --- a/platform/backend/src/routes/proxy/adapters/bedrock-toon-compression.test.ts +++ b/platform/backend/src/routes/proxy/adapters/bedrock-toon-compression.test.ts @@ -1,43 +1,26 @@ // Pins the Bedrock adapter's TOON compression cutover to the native addon: // full transformed-request exact equality (TOON content from the committed v3 -// golden corpus, everything else byte-equal) and the exact -// ToolCompressionStats accounting matrix. Bedrock-specific semantics pinned -// here: compression is applied UNCONDITIONALLY (no keep/reject — encoded -// tokens are always recorded, even when TOON is larger), NEITHER branch -// unwraps client wrappers, only content[0] of a toolResult is read, a +// golden corpus, everything else byte-equal). Bedrock-specific semantics +// pinned here: compression is applied UNCONDITIONALLY (no keep/reject — +// encoded tokens are always recorded, even when TOON is larger), NEITHER +// branch unwraps client wrappers, only content[0] of a toolResult is read, a // compressed result replaces the WHOLE content array with one text item (the // json branch is rewritten to text too), and error-status results are skipped // entirely. Requires the built addon: mandatory in CI; locally it skips // visibly — run `pnpm test:native` from platform/backend. -import { readFileSync } from "node:fs"; -import path from "node:path"; -import { ModelModel } from "@/models"; -import { describe, expect, test } from "@/test"; -import { getTokenizer } from "@/tokenizers"; +import { expect, test } from "@/test"; +import { + corpusEntry, + describeNative, + makeCountTokens, + upsertOneDollarPerTokenPricing, +} from "@/test/toon-golden"; import type { Bedrock } from "@/types"; import { bedrockAdapterFactory } from "./bedrock"; type BedrockRequest = Bedrock.Types.ConverseRequest; -type GoldenEntry = { - name: string; - rawContent: string; - unwrap: boolean; - expected: { normalized: string; encoded: string | null }; -}; - -const CORPUS_PATH = path.resolve( - import.meta.dirname, - "../../../../../archestra-rs/proxy-transform-core/tests/fixtures/golden-corpus.json", -); -const corpus: GoldenEntry[] = JSON.parse(readFileSync(CORPUS_PATH, "utf8")); -function corpusEntry(name: string): GoldenEntry { - const entry = corpus.find((candidate) => candidate.name === name); - if (!entry) throw new Error(`golden corpus entry not found: ${name}`); - return entry; -} - // Corpus picks: a uniform array (TOON smaller), malformed JSON (kept as-is), // a json-branch twin of the uniform array, and a wrapped [{type:"text",...}] // payload encoded WITHOUT unwrapping (its TOON is the wrapper array itself @@ -52,51 +35,22 @@ const WRAPPED_NO_UNWRAP = corpusEntry("wrapped-but-unwrap-false"); const JSON_LARGER = corpusEntry("boundary-obj-3"); // Bedrock accounting uses the Anthropic tokenizer as an approximation. -const tokenizer = getTokenizer("anthropic"); -const countTokens = (content: string) => - tokenizer.countTokens([{ role: "user", content }]); +const countTokens = makeCountTokens("anthropic"); // The json branch tokenizes the adapter's own serialization of the json // value — recompute it here instead of trusting the corpus rawContent. const jsonBranchSerialized = JSON.stringify(JSON.parse(JSON_BRANCH.rawContent)); const BEDROCK_MODEL = "anthropic.claude-sonnet-4-5-20250929-v1:0"; -// $1,000,000 per million input tokens = $1 per token, so expected costSavings -// equals tokens saved exactly. -async function upsertOneDollarPerTokenPricing() { - await ModelModel.upsert({ - externalId: `bedrock/${BEDROCK_MODEL}`, +const upsertPricing = () => + upsertOneDollarPerTokenPricing({ provider: "bedrock", modelId: BEDROCK_MODEL, - inputModalities: null, - outputModalities: null, - customPricePerMillionInput: "1000000.00", - customPricePerMillionOutput: "1000000.00", - lastSyncedAt: new Date(), }); -} - -const addonLoadError: unknown = await import( - "@archestra/proxy-transform-rs" -).then( - () => null, - (error) => error, -); - -// In CI the suite must FAIL (never skip) when the addon is missing. -const describeNative = - addonLoadError === null || process.env.CI ? describe : describe.skip; -if (addonLoadError !== null && !process.env.CI) { - console.warn( - `[bedrock-toon-compression.test] skipping: @archestra/proxy-transform-rs is not built (${String( - addonLoadError, - )}). Run \`pnpm test:native\` from platform/backend.`, - ); -} describeNative("Bedrock adapter TOON compression (native addon)", () => { test("transforms the full provider request exactly (goldens for TOON, byte-equal elsewhere)", async () => { - await upsertOneDollarPerTokenPricing(); + await upsertPricing(); const makeRequest = (): BedrockRequest => ({ modelId: BEDROCK_MODEL, @@ -193,7 +147,7 @@ describeNative("Bedrock adapter TOON compression (native addon)", () => { }); test("applies native results to the right blocks when non-candidate blocks are interleaved", async () => { - await upsertOneDollarPerTokenPricing(); + await upsertPricing(); // Non-candidates between candidates: a plain text block, an error-status // toolResult (whose content WOULD compress if wrongly collected), an @@ -272,7 +226,7 @@ describeNative("Bedrock adapter TOON compression (native addon)", () => { }); test("reads only content[0] and replaces the whole content array (multi-item content)", async () => { - await upsertOneDollarPerTokenPricing(); + await upsertPricing(); const makeRequest = (): BedrockRequest => ({ modelId: BEDROCK_MODEL, @@ -329,211 +283,175 @@ describeNative("Bedrock adapter TOON compression (native addon)", () => { }); }); - describe("exact ToolCompressionStats accounting matrix", () => { - // hadToolResults is true even for malformed content: Bedrock counts every - // non-error toolResult block it sees. There is no reject rule — when the - // encoding is larger it is still applied and both totals recorded. - const rows: { - row: string; - entry: GoldenEntry; - compressed: boolean; - counted: boolean; - }[] = [ - { row: "malformed", entry: MALFORMED, compressed: false, counted: false }, - { - row: "unconditional apply (encoded larger, no unwrap, still applied)", - entry: WRAPPED_NO_UNWRAP, - compressed: true, - counted: true, - }, - { row: "effective", entry: UNIFORM, compressed: true, counted: true }, - ]; + // Cases the combined request cannot isolate: hadToolResults is true even + // for malformed-only content (Bedrock counts every non-error toolResult), + // there is no reject rule on either branch, and error-status results are + // skipped entirely. + test("a lone malformed result: zeroed totals but hadToolResults=true, message untouched", async () => { + await upsertPricing(); - for (const { row, entry, compressed, counted } of rows) { - test(row, async () => { - await upsertOneDollarPerTokenPricing(); - - const adapter = bedrockAdapterFactory.createRequestAdapter({ - modelId: BEDROCK_MODEL, - messages: [ - { role: "user", content: [{ text: "run the tool" }] }, + const makeRequest = (): BedrockRequest => ({ + modelId: BEDROCK_MODEL, + messages: [ + { role: "user", content: [{ text: "run the tool" }] }, + { + role: "user", + content: [ { - role: "user", - content: [ - { - toolResult: { - toolUseId: "tooluse_1", - content: [{ text: entry.rawContent }], - }, - }, - ], + toolResult: { + toolUseId: "tooluse_1", + content: [{ text: MALFORMED.rawContent }], + }, }, ], - }); - const stats = await adapter.applyToonCompression(BEDROCK_MODEL); - - const tokensBefore = counted ? countTokens(entry.rawContent) : 0; - const tokensAfter = compressed - ? countTokens(entry.expected.encoded as string) - : tokensBefore; - expect(stats).toStrictEqual({ - tokensBefore, - tokensAfter, - costSavings: Math.max(0, tokensBefore - tokensAfter), - wasEffective: tokensAfter < tokensBefore, - hadToolResults: true, - }); - - const [, toolMessage] = adapter.getProviderMessages(); - expect(toolMessage).toStrictEqual({ + }, + ], + }); + + const adapter = bedrockAdapterFactory.createRequestAdapter(makeRequest()); + const stats = await adapter.applyToonCompression(BEDROCK_MODEL); + + expect(stats).toStrictEqual({ + tokensBefore: 0, + tokensAfter: 0, + costSavings: 0, + wasEffective: false, + hadToolResults: true, + }); + expect(adapter.toProviderRequest()).toStrictEqual(makeRequest()); + }); + + test("text branch unconditional apply (encoded larger, no unwrap, still applied)", async () => { + await upsertPricing(); + + const adapter = bedrockAdapterFactory.createRequestAdapter({ + modelId: BEDROCK_MODEL, + messages: [ + { role: "user", content: [{ text: "run the tool" }] }, + { role: "user", content: [ { toolResult: { toolUseId: "tooluse_1", - content: [ - { - text: compressed - ? (entry.expected.encoded as string) - : entry.rawContent, - }, - ], + content: [{ text: WRAPPED_NO_UNWRAP.rawContent }], }, }, ], - }); - }); - } - - test("json branch (encodes the json value, unconditional apply)", async () => { - await upsertOneDollarPerTokenPricing(); - - const adapter = bedrockAdapterFactory.createRequestAdapter({ - modelId: BEDROCK_MODEL, - messages: [ - { role: "user", content: [{ text: "run the tool" }] }, - { - role: "user", - content: [ - { - toolResult: { - toolUseId: "tooluse_json", - content: [{ json: JSON.parse(JSON_BRANCH.rawContent) }], - }, - }, - ], - }, - ], - }); - const stats = await adapter.applyToonCompression(BEDROCK_MODEL); - - const tokensBefore = countTokens(jsonBranchSerialized); - const tokensAfter = countTokens(JSON_BRANCH.expected.encoded as string); - expect(stats).toStrictEqual({ - tokensBefore, - tokensAfter, - costSavings: tokensBefore - tokensAfter, - wasEffective: true, - hadToolResults: true, - }); - - const [, toolMessage] = adapter.getProviderMessages(); - expect(toolMessage).toStrictEqual({ - role: "user", - content: [ - { - toolResult: { - toolUseId: "tooluse_json", - content: [{ text: JSON_BRANCH.expected.encoded as string }], - }, + }, + ], + }); + const stats = await adapter.applyToonCompression(BEDROCK_MODEL); + + const tokensBefore = countTokens(WRAPPED_NO_UNWRAP.rawContent); + const tokensAfter = countTokens( + WRAPPED_NO_UNWRAP.expected.encoded as string, + ); + expect(tokensAfter).toBeGreaterThan(tokensBefore); + expect(stats).toStrictEqual({ + tokensBefore, + tokensAfter, + costSavings: 0, + wasEffective: false, + hadToolResults: true, + }); + + const [, toolMessage] = adapter.getProviderMessages(); + expect(toolMessage).toStrictEqual({ + role: "user", + content: [ + { + toolResult: { + toolUseId: "tooluse_1", + content: [{ text: WRAPPED_NO_UNWRAP.expected.encoded as string }], }, - ], - }); + }, + ], }); + }); + + test("json branch unconditional apply (encoded larger, still applied)", async () => { + await upsertPricing(); - test("json branch unconditional apply (encoded larger, still applied)", async () => { - await upsertOneDollarPerTokenPricing(); - - const jsonLargerSerialized = JSON.stringify( - JSON.parse(JSON_LARGER.rawContent), - ); - const adapter = bedrockAdapterFactory.createRequestAdapter({ - modelId: BEDROCK_MODEL, - messages: [ - { role: "user", content: [{ text: "run the tool" }] }, - { - role: "user", - content: [ - { - toolResult: { - toolUseId: "tooluse_json_larger", - content: [{ json: JSON.parse(JSON_LARGER.rawContent) }], - }, + const jsonLargerSerialized = JSON.stringify( + JSON.parse(JSON_LARGER.rawContent), + ); + const adapter = bedrockAdapterFactory.createRequestAdapter({ + modelId: BEDROCK_MODEL, + messages: [ + { role: "user", content: [{ text: "run the tool" }] }, + { + role: "user", + content: [ + { + toolResult: { + toolUseId: "tooluse_json_larger", + content: [{ json: JSON.parse(JSON_LARGER.rawContent) }], }, - ], - }, - ], - }); - const stats = await adapter.applyToonCompression(BEDROCK_MODEL); - - const tokensBefore = countTokens(jsonLargerSerialized); - const tokensAfter = countTokens(JSON_LARGER.expected.encoded as string); - expect(tokensAfter).toBeGreaterThan(tokensBefore); - expect(stats).toStrictEqual({ - tokensBefore, - tokensAfter, - costSavings: 0, - wasEffective: false, - hadToolResults: true, - }); - - // Still applied despite being larger — no keep/reject on this branch. - const [, toolMessage] = adapter.getProviderMessages(); - expect(toolMessage).toStrictEqual({ - role: "user", - content: [ - { - toolResult: { - toolUseId: "tooluse_json_larger", - content: [{ text: JSON_LARGER.expected.encoded as string }], }, + ], + }, + ], + }); + const stats = await adapter.applyToonCompression(BEDROCK_MODEL); + + const tokensBefore = countTokens(jsonLargerSerialized); + const tokensAfter = countTokens(JSON_LARGER.expected.encoded as string); + expect(tokensAfter).toBeGreaterThan(tokensBefore); + expect(stats).toStrictEqual({ + tokensBefore, + tokensAfter, + costSavings: 0, + wasEffective: false, + hadToolResults: true, + }); + + // Still applied despite being larger — no keep/reject on this branch. + const [, toolMessage] = adapter.getProviderMessages(); + expect(toolMessage).toStrictEqual({ + role: "user", + content: [ + { + toolResult: { + toolUseId: "tooluse_json_larger", + content: [{ text: JSON_LARGER.expected.encoded as string }], }, - ], - }); + }, + ], }); + }); + + test("error-status result is skipped entirely (not counted as a tool result)", async () => { + await upsertPricing(); - test("error-status result is skipped entirely (not counted as a tool result)", async () => { - await upsertOneDollarPerTokenPricing(); - - const makeRequest = (): BedrockRequest => ({ - modelId: BEDROCK_MODEL, - messages: [ - { - role: "user", - content: [ - { - toolResult: { - toolUseId: "tooluse_error", - content: [{ text: UNIFORM.rawContent }], - status: "error", - }, + const makeRequest = (): BedrockRequest => ({ + modelId: BEDROCK_MODEL, + messages: [ + { + role: "user", + content: [ + { + toolResult: { + toolUseId: "tooluse_error", + content: [{ text: UNIFORM.rawContent }], + status: "error", }, - ], - }, - ], - }); - - const adapter = bedrockAdapterFactory.createRequestAdapter(makeRequest()); - const stats = await adapter.applyToonCompression(BEDROCK_MODEL); - - expect(adapter.toProviderRequest()).toStrictEqual(makeRequest()); - expect(stats).toStrictEqual({ - tokensBefore: 0, - tokensAfter: 0, - costSavings: 0, - wasEffective: false, - hadToolResults: false, - }); + }, + ], + }, + ], + }); + + const adapter = bedrockAdapterFactory.createRequestAdapter(makeRequest()); + const stats = await adapter.applyToonCompression(BEDROCK_MODEL); + + expect(adapter.toProviderRequest()).toStrictEqual(makeRequest()); + expect(stats).toStrictEqual({ + tokensBefore: 0, + tokensAfter: 0, + costSavings: 0, + wasEffective: false, + hadToolResults: false, }); }); }); diff --git a/platform/backend/src/routes/proxy/adapters/cohere-toon-compression.test.ts b/platform/backend/src/routes/proxy/adapters/cohere-toon-compression.test.ts index 304790c6c23..9b71b9e216d 100644 --- a/platform/backend/src/routes/proxy/adapters/cohere-toon-compression.test.ts +++ b/platform/backend/src/routes/proxy/adapters/cohere-toon-compression.test.ts @@ -1,41 +1,26 @@ // Pins the Cohere adapter's TOON compression cutover to the native addon: // full transformed-messages exact equality (TOON content from the committed -// v3 golden corpus, everything else byte-equal) and the exact -// ToolCompressionStats accounting matrix. Cohere-specific semantics pinned -// here: totals are updated ONLY on wins — a rejected (not smaller) payload +// v3 golden corpus, everything else byte-equal) plus the Cohere-specific +// wins-only accounting the combined request cannot isolate: totals are +// updated ONLY on wins — a rejected (not smaller) or malformed payload // contributes nothing to either total, so hadToolResults (totalTokensBefore > // 0) is false when no result compresses. Requires the built addon: mandatory // in CI; locally it skips visibly — run `pnpm test:native` from // platform/backend. -import { readFileSync } from "node:fs"; -import path from "node:path"; -import { ModelModel } from "@/models"; -import { describe, expect, test } from "@/test"; -import { getTokenizer } from "@/tokenizers"; +import { expect, test } from "@/test"; +import { + corpusEntry, + describeNative, + makeCountTokens, + makeToolCall, + upsertOneDollarPerTokenPricing, +} from "@/test/toon-golden"; import type { Cohere } from "@/types"; import { cohereAdapterFactory } from "./cohere"; type CohereRequest = Cohere.Types.ChatRequest; -type GoldenEntry = { - name: string; - rawContent: string; - unwrap: boolean; - expected: { normalized: string; encoded: string | null }; -}; - -const CORPUS_PATH = path.resolve( - import.meta.dirname, - "../../../../../archestra-rs/proxy-transform-core/tests/fixtures/golden-corpus.json", -); -const corpus: GoldenEntry[] = JSON.parse(readFileSync(CORPUS_PATH, "utf8")); -function corpusEntry(name: string): GoldenEntry { - const entry = corpus.find((candidate) => candidate.name === name); - if (!entry) throw new Error(`golden corpus entry not found: ${name}`); - return entry; -} - // Corpus picks: a uniform array (compression wins), a wrapped // [{type:"text",...}] payload (unwrapped, compression wins), malformed JSON // (kept as-is), and a near-boundary object whose TOON encoding does not save @@ -46,54 +31,16 @@ const WRAPPED = corpusEntry("wrapped-single-text"); const MALFORMED = corpusEntry("malformed-prose"); const NEAR_BOUNDARY = corpusEntry("boundary-obj-1"); -const tokenizer = getTokenizer("cohere"); -const countTokens = (content: string) => - tokenizer.countTokens([{ role: "user", content }]); - -// $1,000,000 per million input tokens = $1 per token, so expected costSavings -// equals tokens saved exactly. -async function upsertOneDollarPerTokenPricing() { - await ModelModel.upsert({ - externalId: "cohere/command-r-plus", +const countTokens = makeCountTokens("cohere"); +const upsertPricing = () => + upsertOneDollarPerTokenPricing({ provider: "cohere", modelId: "command-r-plus", - inputModalities: null, - outputModalities: null, - customPricePerMillionInput: "1000000.00", - customPricePerMillionOutput: "1000000.00", - lastSyncedAt: new Date(), }); -} - -const addonLoadError: unknown = await import( - "@archestra/proxy-transform-rs" -).then( - () => null, - (error) => error, -); - -// In CI the suite must FAIL (never skip) when the addon is missing. -const describeNative = - addonLoadError === null || process.env.CI ? describe : describe.skip; -if (addonLoadError !== null && !process.env.CI) { - console.warn( - `[cohere-toon-compression.test] skipping: @archestra/proxy-transform-rs is not built (${String( - addonLoadError, - )}). Run \`pnpm test:native\` from platform/backend.`, - ); -} - -function makeToolCall(id: string, name: string) { - return { - id, - type: "function" as const, - function: { name, arguments: '{"directory":"."}' }, - }; -} describeNative("Cohere adapter TOON compression (native addon)", () => { test("transforms the full messages exactly (goldens for TOON, byte-equal elsewhere)", async () => { - await upsertOneDollarPerTokenPricing(); + await upsertPricing(); const makeRequest = (): CohereRequest => ({ model: "command-r-plus", @@ -170,7 +117,7 @@ describeNative("Cohere adapter TOON compression (native addon)", () => { }); test("applies native results to the right candidates when non-candidate messages are interleaved", async () => { - await upsertOneDollarPerTokenPricing(); + await upsertPricing(); // Cohere tool messages are string-only, so non-candidates here are // assistant/user messages between tool messages: the native result index @@ -233,60 +180,37 @@ describeNative("Cohere adapter TOON compression (native addon)", () => { }); }); - describe("exact ToolCompressionStats accounting matrix", () => { - // Wins-only accounting: rejected and malformed rows contribute nothing, - // so hadToolResults (totalTokensBefore > 0) is false for both. - const rows: { - row: string; - entry: GoldenEntry; - compressed: boolean; - }[] = [ - { row: "malformed", entry: MALFORMED, compressed: false }, - { - row: "ineffective (rejected: counted in NEITHER total)", - entry: NEAR_BOUNDARY, - compressed: false, - }, - { row: "effective", entry: UNIFORM, compressed: true }, - { row: "wrapped-array", entry: WRAPPED, compressed: true }, - ]; - - for (const { row, entry, compressed } of rows) { - test(row, async () => { - await upsertOneDollarPerTokenPricing(); - - const adapter = cohereAdapterFactory.createRequestAdapter({ - model: "command-r-plus", - messages: [ - { role: "user", content: "run the tool" }, - { role: "tool", tool_call_id: "call_1", content: entry.rawContent }, - ], - }); - const stats = await adapter.applyToonCompression("command-r-plus"); - - const tokensBefore = compressed - ? countTokens(entry.expected.normalized) - : 0; - const tokensAfter = compressed - ? countTokens(entry.expected.encoded as string) - : 0; - expect(stats).toStrictEqual({ - tokensBefore, - tokensAfter, - costSavings: tokensBefore - tokensAfter, - wasEffective: compressed, - hadToolResults: compressed, - }); - - const [, toolMessage] = adapter.getProviderMessages(); - expect(toolMessage).toStrictEqual({ - role: "tool", - tool_call_id: "call_1", - content: compressed - ? (entry.expected.encoded as string) - : entry.rawContent, - }); + // Wins-only accounting isolated: with no win, NOTHING is counted — even a + // parseable rejected payload — so hadToolResults is false for both cases. + for (const [row, entry] of [ + ["malformed", MALFORMED], + ["rejected (not smaller)", NEAR_BOUNDARY], + ] as const) { + test(`a lone ${row} result: nothing counted, hadToolResults=false, message untouched`, async () => { + await upsertPricing(); + + const adapter = cohereAdapterFactory.createRequestAdapter({ + model: "command-r-plus", + messages: [ + { role: "user", content: "run the tool" }, + { role: "tool", tool_call_id: "call_1", content: entry.rawContent }, + ], }); - } - }); + const stats = await adapter.applyToonCompression("command-r-plus"); + + expect(stats).toStrictEqual({ + tokensBefore: 0, + tokensAfter: 0, + costSavings: 0, + wasEffective: false, + hadToolResults: false, + }); + const [, toolMessage] = adapter.getProviderMessages(); + expect(toolMessage).toStrictEqual({ + role: "tool", + tool_call_id: "call_1", + content: entry.rawContent, + }); + }); + } }); diff --git a/platform/backend/src/routes/proxy/adapters/gemini-toon-compression.test.ts b/platform/backend/src/routes/proxy/adapters/gemini-toon-compression.test.ts index 2e55923d133..906f0297987 100644 --- a/platform/backend/src/routes/proxy/adapters/gemini-toon-compression.test.ts +++ b/platform/backend/src/routes/proxy/adapters/gemini-toon-compression.test.ts @@ -1,43 +1,28 @@ // Pins the Gemini adapter's TOON compression cutover to the native addon: // full transformed-contents exact equality (TOON content from the committed -// v3 golden corpus, everything else byte-equal) and the exact -// ToolCompressionStats accounting matrix. Gemini-specific semantics pinned -// here: the adapter serializes functionResponse.response itself and tokenizes -// that ORIGINAL serialization (not the unwrapped string) while parsing goes -// through the unwrap path, a winning part is replaced with -// { functionResponse: { ..., response: { tool_result: "" } } }, and -// rejected payloads count their original tokens in both totals. Requires the +// v3 golden corpus, everything else byte-equal). Gemini-specific semantics +// pinned here: the adapter serializes functionResponse.response itself and +// tokenizes that ORIGINAL serialization (not the unwrapped string) while +// parsing goes through the unwrap path, a winning part is replaced with +// { functionResponse: { ..., response: { tool_result: "" } } }, rejected +// payloads count their original tokens in both totals, and hadToolResults +// reflects every functionResponse part (even unparseable ones). Requires the // built addon: mandatory in CI; locally it skips visibly — run // `pnpm test:native` from platform/backend. -import { readFileSync } from "node:fs"; -import path from "node:path"; -import { ModelModel } from "@/models"; -import { describe, expect, test } from "@/test"; -import { getTokenizer } from "@/tokenizers"; +import { expect, test } from "@/test"; +import { + corpusEntry, + describeNative, + type GoldenEntry, + makeCountTokens, + upsertOneDollarPerTokenPricing, +} from "@/test/toon-golden"; import type { Gemini } from "@/types"; import { geminiAdapterFactory } from "./gemini"; type GeminiRequest = Gemini.Types.GenerateContentRequest; -type GoldenEntry = { - name: string; - rawContent: string; - unwrap: boolean; - expected: { normalized: string; encoded: string | null }; -}; - -const CORPUS_PATH = path.resolve( - import.meta.dirname, - "../../../../../archestra-rs/proxy-transform-core/tests/fixtures/golden-corpus.json", -); -const corpus: GoldenEntry[] = JSON.parse(readFileSync(CORPUS_PATH, "utf8")); -function corpusEntry(name: string): GoldenEntry { - const entry = corpus.find((candidate) => candidate.name === name); - if (!entry) throw new Error(`golden corpus entry not found: ${name}`); - return entry; -} - // Corpus picks: a uniform array (compression wins), a wrapped // [{type:"text",...}] payload (unwrapped for parsing, compression wins), a // wrapper whose inner text is NOT JSON (the "cannot be compressed" path — a @@ -49,50 +34,21 @@ const WRAPPED = corpusEntry("wrapped-single-text"); const UNPARSEABLE = corpusEntry("wrapped-text-not-json"); const NEAR_BOUNDARY = corpusEntry("boundary-obj-1"); -const tokenizer = getTokenizer("gemini"); -const countTokens = (content: string) => - tokenizer.countTokens([{ role: "user", content }]); +const countTokens = makeCountTokens("gemini"); // Gemini accounting uses the adapter's own serialization of the response // object — recompute it here instead of trusting the corpus rawContent. const serialized = (entry: GoldenEntry) => JSON.stringify(JSON.parse(entry.rawContent)); -// $1,000,000 per million input tokens = $1 per token, so expected costSavings -// equals tokens saved exactly. -async function upsertOneDollarPerTokenPricing() { - await ModelModel.upsert({ - externalId: "gemini/gemini-2.0-flash", +const upsertPricing = () => + upsertOneDollarPerTokenPricing({ provider: "gemini", modelId: "gemini-2.0-flash", - inputModalities: null, - outputModalities: null, - customPricePerMillionInput: "1000000.00", - customPricePerMillionOutput: "1000000.00", - lastSyncedAt: new Date(), }); -} - -const addonLoadError: unknown = await import( - "@archestra/proxy-transform-rs" -).then( - () => null, - (error) => error, -); - -// In CI the suite must FAIL (never skip) when the addon is missing. -const describeNative = - addonLoadError === null || process.env.CI ? describe : describe.skip; -if (addonLoadError !== null && !process.env.CI) { - console.warn( - `[gemini-toon-compression.test] skipping: @archestra/proxy-transform-rs is not built (${String( - addonLoadError, - )}). Run \`pnpm test:native\` from platform/backend.`, - ); -} describeNative("Gemini adapter TOON compression (native addon)", () => { test("transforms the full contents exactly (goldens for TOON, byte-equal elsewhere)", async () => { - await upsertOneDollarPerTokenPricing(); + await upsertPricing(); const makeRequest = (): GeminiRequest => ({ contents: [ @@ -180,7 +136,7 @@ describeNative("Gemini adapter TOON compression (native addon)", () => { }); test("applies native results to the right parts when non-candidate parts are interleaved", async () => { - await upsertOneDollarPerTokenPricing(); + await upsertPricing(); // Text parts and a model turn between functionResponse parts: the // winning candidate sits at native index 2, content index 3, part index @@ -254,83 +210,73 @@ describeNative("Gemini adapter TOON compression (native addon)", () => { }); }); - describe("exact ToolCompressionStats accounting matrix", () => { - // hadToolResults is true even for the unparseable row: Gemini counts - // every functionResponse part it sees. - const rows: { - row: string; - entry: GoldenEntry; - compressed: boolean; - counted: boolean; - }[] = [ - { - row: "unparseable after unwrap", - entry: UNPARSEABLE, - compressed: false, - counted: false, - }, - { - row: "ineffective (rejected: original counted in both totals)", - entry: NEAR_BOUNDARY, - compressed: false, - counted: true, - }, - { row: "effective", entry: UNIFORM, compressed: true, counted: true }, + // The case the combined request cannot isolate: hadToolResults is true even + // when the only functionResponse is unparseable after unwrap — Gemini + // counts every functionResponse part it sees. + test("a lone unparseable response: zeroed totals but hadToolResults=true, contents untouched", async () => { + await upsertPricing(); + + const makeContents = (): GeminiRequest["contents"] => [ + { role: "user", parts: [{ text: "run the tool" }] }, { - row: "wrapped-array (tokenized as the whole wrapper serialization)", - entry: WRAPPED, - compressed: true, - counted: true, + role: "user", + parts: [ + { + functionResponse: { + name: "the_tool", + response: JSON.parse(UNPARSEABLE.rawContent), + }, + }, + ], }, ]; - for (const { row, entry, compressed, counted } of rows) { - test(row, async () => { - await upsertOneDollarPerTokenPricing(); - - const makeContents = (): GeminiRequest["contents"] => [ - { role: "user", parts: [{ text: "run the tool" }] }, - { - role: "user", - parts: [ - { - functionResponse: { - name: "the_tool", - response: JSON.parse(entry.rawContent), - }, - }, - ], - }, - ]; + const adapter = geminiAdapterFactory.createRequestAdapter({ + contents: makeContents(), + }); + const stats = await adapter.applyToonCompression("gemini-2.0-flash"); - const adapter = geminiAdapterFactory.createRequestAdapter({ - contents: makeContents(), - }); - const stats = await adapter.applyToonCompression("gemini-2.0-flash"); + expect(stats).toStrictEqual({ + tokensBefore: 0, + tokensAfter: 0, + costSavings: 0, + wasEffective: false, + hadToolResults: true, + }); + expect(adapter.getProviderMessages()).toStrictEqual(makeContents()); + }); - const tokensBefore = counted ? countTokens(serialized(entry)) : 0; - const tokensAfter = compressed - ? countTokens(entry.expected.encoded as string) - : tokensBefore; - expect(stats).toStrictEqual({ - tokensBefore, - tokensAfter, - costSavings: tokensBefore - tokensAfter, - wasEffective: compressed, - hadToolResults: true, - }); + test("a lone rejected response: original counted in both totals, wasEffective=false", async () => { + await upsertPricing(); - const expectedContents = makeContents(); - if (compressed) { - expectedContents[1].parts[0] = { + const makeContents = (): GeminiRequest["contents"] => [ + { role: "user", parts: [{ text: "run the tool" }] }, + { + role: "user", + parts: [ + { functionResponse: { name: "the_tool", - response: { tool_result: entry.expected.encoded as string }, + response: JSON.parse(NEAR_BOUNDARY.rawContent), }, - }; - } - expect(adapter.getProviderMessages()).toStrictEqual(expectedContents); - }); - } + }, + ], + }, + ]; + + const adapter = geminiAdapterFactory.createRequestAdapter({ + contents: makeContents(), + }); + const stats = await adapter.applyToonCompression("gemini-2.0-flash"); + + const boundaryTokens = countTokens(serialized(NEAR_BOUNDARY)); + expect(stats).toStrictEqual({ + tokensBefore: boundaryTokens, + tokensAfter: boundaryTokens, + costSavings: 0, + wasEffective: false, + hadToolResults: true, + }); + expect(adapter.getProviderMessages()).toStrictEqual(makeContents()); }); }); diff --git a/platform/backend/src/routes/proxy/adapters/minimax-toon-compression.test.ts b/platform/backend/src/routes/proxy/adapters/minimax-toon-compression.test.ts index 758560d1d3e..dd47d2d5b3a 100644 --- a/platform/backend/src/routes/proxy/adapters/minimax-toon-compression.test.ts +++ b/platform/backend/src/routes/proxy/adapters/minimax-toon-compression.test.ts @@ -1,40 +1,24 @@ // Pins the MiniMax adapter's TOON compression cutover to the native addon: -// full transformed-request exact equality (TOON content from the committed v3 -// golden corpus, everything else byte-equal) and the exact -// ToolCompressionStats accounting matrix. MiniMax-specific semantics pinned -// here: compression is applied UNCONDITIONALLY (no keep/reject — encoded -// tokens are always recorded, even when TOON is larger). Requires the built -// addon: mandatory in CI; locally it skips visibly — run `pnpm test:native` -// from platform/backend. - -import { readFileSync } from "node:fs"; -import path from "node:path"; -import { ModelModel } from "@/models"; -import { describe, expect, test } from "@/test"; -import { getTokenizer } from "@/tokenizers"; +// full transformed-messages exact equality (TOON content from the committed +// v3 golden corpus, everything else byte-equal). MiniMax-specific semantics +// pinned here: compression is applied UNCONDITIONALLY (no keep/reject — +// encoded tokens are always recorded, even when TOON is larger). Requires the +// built addon: mandatory in CI; locally it skips visibly — run +// `pnpm test:native` from platform/backend. + +import { expect, test } from "@/test"; +import { + corpusEntry, + describeNative, + makeCountTokens, + makeToolCall, + upsertOneDollarPerTokenPricing, +} from "@/test/toon-golden"; import type { Minimax } from "@/types/llm-providers"; import { minimaxAdapterFactory } from "./minimax"; type MinimaxRequest = Minimax.Types.ChatCompletionsRequest; -type GoldenEntry = { - name: string; - rawContent: string; - unwrap: boolean; - expected: { normalized: string; encoded: string | null }; -}; - -const CORPUS_PATH = path.resolve( - import.meta.dirname, - "../../../../../archestra-rs/proxy-transform-core/tests/fixtures/golden-corpus.json", -); -const corpus: GoldenEntry[] = JSON.parse(readFileSync(CORPUS_PATH, "utf8")); -function corpusEntry(name: string): GoldenEntry { - const entry = corpus.find((candidate) => candidate.name === name); - if (!entry) throw new Error(`golden corpus entry not found: ${name}`); - return entry; -} - // Corpus picks: a uniform array (TOON smaller), a wrapped [{type:"text",...}] // payload (unwrapped, TOON smaller), malformed JSON (kept as-is), and a // heterogeneous array whose TOON encoding is LARGER under the MiniMax @@ -44,54 +28,16 @@ const WRAPPED = corpusEntry("wrapped-single-text"); const MALFORMED = corpusEntry("malformed-prose"); const LARGER = corpusEntry("boundary-hetero-arr-1"); -const tokenizer = getTokenizer("minimax"); -const countTokens = (content: string) => - tokenizer.countTokens([{ role: "user", content }]); - -// $1,000,000 per million input tokens = $1 per token, so expected costSavings -// equals tokens saved exactly. -async function upsertOneDollarPerTokenPricing() { - await ModelModel.upsert({ - externalId: "minimax/MiniMax-M2", +const countTokens = makeCountTokens("minimax"); +const upsertPricing = () => + upsertOneDollarPerTokenPricing({ provider: "minimax", modelId: "MiniMax-M2", - inputModalities: null, - outputModalities: null, - customPricePerMillionInput: "1000000.00", - customPricePerMillionOutput: "1000000.00", - lastSyncedAt: new Date(), }); -} - -const addonLoadError: unknown = await import( - "@archestra/proxy-transform-rs" -).then( - () => null, - (error) => error, -); - -// In CI the suite must FAIL (never skip) when the addon is missing. -const describeNative = - addonLoadError === null || process.env.CI ? describe : describe.skip; -if (addonLoadError !== null && !process.env.CI) { - console.warn( - `[minimax-toon-compression.test] skipping: @archestra/proxy-transform-rs is not built (${String( - addonLoadError, - )}). Run \`pnpm test:native\` from platform/backend.`, - ); -} - -function makeToolCall(id: string, name: string) { - return { - id, - type: "function" as const, - function: { name, arguments: '{"directory":"."}' }, - }; -} describeNative("MiniMax adapter TOON compression (native addon)", () => { test("transforms the full messages exactly (goldens for TOON, byte-equal elsewhere)", async () => { - await upsertOneDollarPerTokenPricing(); + await upsertPricing(); const makeRequest = (): MinimaxRequest => ({ model: "MiniMax-M2", @@ -175,7 +121,7 @@ describeNative("MiniMax adapter TOON compression (native addon)", () => { }); test("applies native results to the right candidates when non-string tool messages are interleaved", async () => { - await upsertOneDollarPerTokenPricing(); + await upsertPricing(); // Non-string (array-content) tool messages are NOT candidates for the // native batch: the native result index diverges from both the message @@ -236,65 +182,61 @@ describeNative("MiniMax adapter TOON compression (native addon)", () => { }); }); - describe("exact ToolCompressionStats accounting matrix", () => { - const rows: { - row: string; - entry: GoldenEntry; - compressed: boolean; - counted: boolean; - }[] = [ - { row: "malformed", entry: MALFORMED, compressed: false, counted: false }, - { - row: "unconditional apply (encoded larger, still applied and recorded)", - entry: LARGER, - compressed: true, - counted: true, - }, - { row: "effective", entry: UNIFORM, compressed: true, counted: true }, - { - row: "wrapped-array", - entry: WRAPPED, - compressed: true, - counted: true, - }, - ]; + // The two cases the combined request cannot isolate. + test("a lone malformed result: zeroed stats (hadToolResults=false), message untouched", async () => { + await upsertPricing(); - for (const { row, entry, compressed, counted } of rows) { - test(row, async () => { - await upsertOneDollarPerTokenPricing(); + const adapter = minimaxAdapterFactory.createRequestAdapter({ + model: "MiniMax-M2", + messages: [ + { role: "user", content: "run the tool" }, + { role: "tool", tool_call_id: "call_1", content: MALFORMED.rawContent }, + ], + }); + const stats = await adapter.applyToonCompression("MiniMax-M2"); - const adapter = minimaxAdapterFactory.createRequestAdapter({ - model: "MiniMax-M2", - messages: [ - { role: "user", content: "run the tool" }, - { role: "tool", tool_call_id: "call_1", content: entry.rawContent }, - ], - }); - const stats = await adapter.applyToonCompression("MiniMax-M2"); + expect(stats).toStrictEqual({ + tokensBefore: 0, + tokensAfter: 0, + costSavings: 0, + wasEffective: false, + hadToolResults: false, + }); + const [, toolMessage] = adapter.getProviderMessages(); + expect(toolMessage).toStrictEqual({ + role: "tool", + tool_call_id: "call_1", + content: MALFORMED.rawContent, + }); + }); - const tokensBefore = counted - ? countTokens(entry.expected.normalized) - : 0; - const tokensAfter = compressed - ? countTokens(entry.expected.encoded as string) - : tokensBefore; - expect(stats).toStrictEqual({ - tokensBefore, - tokensAfter, - costSavings: Math.max(0, tokensBefore - tokensAfter), - wasEffective: tokensAfter < tokensBefore, - hadToolResults: counted, - }); + test("unconditional apply: a lone larger encoding is still applied, wasEffective=false", async () => { + await upsertPricing(); - const [, toolMessage] = adapter.getProviderMessages(); - expect(toolMessage).toStrictEqual({ - role: "tool", - tool_call_id: "call_1", - content: compressed - ? (entry.expected.encoded as string) - : entry.rawContent, - }); - }); - } + const adapter = minimaxAdapterFactory.createRequestAdapter({ + model: "MiniMax-M2", + messages: [ + { role: "user", content: "run the tool" }, + { role: "tool", tool_call_id: "call_1", content: LARGER.rawContent }, + ], + }); + const stats = await adapter.applyToonCompression("MiniMax-M2"); + + const tokensBefore = countTokens(LARGER.expected.normalized); + const tokensAfter = countTokens(LARGER.expected.encoded as string); + expect(tokensAfter).toBeGreaterThan(tokensBefore); + expect(stats).toStrictEqual({ + tokensBefore, + tokensAfter, + costSavings: 0, + wasEffective: false, + hadToolResults: true, + }); + const [, toolMessage] = adapter.getProviderMessages(); + expect(toolMessage).toStrictEqual({ + role: "tool", + tool_call_id: "call_1", + content: LARGER.expected.encoded as string, + }); }); }); diff --git a/platform/backend/src/routes/proxy/adapters/openai-toon-compression.test.ts b/platform/backend/src/routes/proxy/adapters/openai-toon-compression.test.ts index 09b43db0d84..c10e349152d 100644 --- a/platform/backend/src/routes/proxy/adapters/openai-toon-compression.test.ts +++ b/platform/backend/src/routes/proxy/adapters/openai-toon-compression.test.ts @@ -1,38 +1,24 @@ // Pins the OpenAI adapter's TOON compression cutover to the native addon: // full transformed-request exact equality (TOON content from the committed v3 -// golden corpus, everything else byte-equal) and the exact -// ToolCompressionStats accounting matrix (rejected payloads counted in BOTH -// totals — the OpenAI-family rule). Requires the built addon: mandatory in -// CI; locally it skips visibly — run `pnpm test:native` from platform/backend. - -import { readFileSync } from "node:fs"; -import path from "node:path"; -import { ModelModel } from "@/models"; -import { describe, expect, test } from "@/test"; -import { getTokenizer } from "@/tokenizers"; +// golden corpus, everything else byte-equal) plus the accounting semantics +// the combined request cannot isolate (hadToolResults=false when nothing was +// parseable; a lone rejected payload counted in BOTH totals — the +// OpenAI-family rule). Requires the built addon: mandatory in CI; locally it +// skips visibly — run `pnpm test:native` from platform/backend. + +import { expect, test } from "@/test"; +import { + corpusEntry, + describeNative, + makeCountTokens, + makeToolCall, + upsertOneDollarPerTokenPricing, +} from "@/test/toon-golden"; import type { OpenAi } from "@/types"; import { openaiAdapterFactory } from "./openai"; type OpenAiRequest = OpenAi.Types.ChatCompletionsRequest; -type GoldenEntry = { - name: string; - rawContent: string; - unwrap: boolean; - expected: { normalized: string; encoded: string | null }; -}; - -const CORPUS_PATH = path.resolve( - import.meta.dirname, - "../../../../../archestra-rs/proxy-transform-core/tests/fixtures/golden-corpus.json", -); -const corpus: GoldenEntry[] = JSON.parse(readFileSync(CORPUS_PATH, "utf8")); -function corpusEntry(name: string): GoldenEntry { - const entry = corpus.find((candidate) => candidate.name === name); - if (!entry) throw new Error(`golden corpus entry not found: ${name}`); - return entry; -} - // Corpus picks (see the corpus for content): a uniform array (compression // wins), a wrapped [{type:"text",...}] payload (unwrapped, compression wins), // malformed JSON (kept as-is), and a near-boundary object whose TOON encoding @@ -42,54 +28,13 @@ const WRAPPED = corpusEntry("wrapped-single-text"); const MALFORMED = corpusEntry("malformed-prose"); const NEAR_BOUNDARY = corpusEntry("boundary-obj-1"); -const tokenizer = getTokenizer("openai"); -const countTokens = (content: string) => - tokenizer.countTokens([{ role: "user", content }]); - -// $1,000,000 per million input tokens = $1 per token, so expected costSavings -// equals tokens saved exactly. -async function upsertOneDollarPerTokenPricing() { - await ModelModel.upsert({ - externalId: "openai/gpt-4o", - provider: "openai", - modelId: "gpt-4o", - inputModalities: null, - outputModalities: null, - customPricePerMillionInput: "1000000.00", - customPricePerMillionOutput: "1000000.00", - lastSyncedAt: new Date(), - }); -} - -const addonLoadError: unknown = await import( - "@archestra/proxy-transform-rs" -).then( - () => null, - (error) => error, -); - -// In CI the suite must FAIL (never skip) when the addon is missing. -const describeNative = - addonLoadError === null || process.env.CI ? describe : describe.skip; -if (addonLoadError !== null && !process.env.CI) { - console.warn( - `[openai-toon-compression.test] skipping: @archestra/proxy-transform-rs is not built (${String( - addonLoadError, - )}). Run \`pnpm test:native\` from platform/backend.`, - ); -} - -function makeToolCall(id: string, name: string) { - return { - id, - type: "function" as const, - function: { name, arguments: '{"directory":"."}' }, - }; -} +const countTokens = makeCountTokens("openai"); +const upsertPricing = () => + upsertOneDollarPerTokenPricing({ provider: "openai", modelId: "gpt-4o" }); describeNative("OpenAI adapter TOON compression (native addon)", () => { test("transforms the full provider request exactly (goldens for TOON, byte-equal elsewhere)", async () => { - await upsertOneDollarPerTokenPricing(); + await upsertPricing(); const makeRequest = (): OpenAiRequest => ({ model: "gpt-4o", @@ -169,7 +114,7 @@ describeNative("OpenAI adapter TOON compression (native addon)", () => { }); test("applies native results to the right candidates when non-string tool messages are interleaved", async () => { - await upsertOneDollarPerTokenPricing(); + await upsertPricing(); // Non-string (array-content) tool messages are NOT candidates for the // native batch. Interleaving them between string candidates means the @@ -246,65 +191,63 @@ describeNative("OpenAI adapter TOON compression (native addon)", () => { }); }); - describe("exact ToolCompressionStats accounting matrix", () => { - const rows: { - row: string; - entry: GoldenEntry; - compressed: boolean; - counted: boolean; - }[] = [ - { row: "malformed", entry: MALFORMED, compressed: false, counted: false }, - { - row: "ineffective (rejected: original counted in both totals)", - entry: NEAR_BOUNDARY, - compressed: false, - counted: true, - }, - { row: "effective", entry: UNIFORM, compressed: true, counted: true }, - { - row: "wrapped-array", - entry: WRAPPED, - compressed: true, - counted: true, - }, - ]; + // The two accounting cases the combined request above cannot isolate. + test("a lone malformed result: zeroed stats (hadToolResults=false), message untouched", async () => { + await upsertPricing(); - for (const { row, entry, compressed, counted } of rows) { - test(row, async () => { - await upsertOneDollarPerTokenPricing(); + const adapter = openaiAdapterFactory.createRequestAdapter({ + model: "gpt-4o", + messages: [ + { role: "user", content: "run the tool" }, + { role: "tool", tool_call_id: "call_1", content: MALFORMED.rawContent }, + ], + }); + const stats = await adapter.applyToonCompression("gpt-4o"); - const adapter = openaiAdapterFactory.createRequestAdapter({ - model: "gpt-4o", - messages: [ - { role: "user", content: "run the tool" }, - { role: "tool", tool_call_id: "call_1", content: entry.rawContent }, - ], - }); - const stats = await adapter.applyToonCompression("gpt-4o"); + expect(stats).toStrictEqual({ + tokensBefore: 0, + tokensAfter: 0, + costSavings: 0, + wasEffective: false, + hadToolResults: false, + }); + const [, toolMessage] = adapter.getProviderMessages(); + expect(toolMessage).toStrictEqual({ + role: "tool", + tool_call_id: "call_1", + content: MALFORMED.rawContent, + }); + }); - const tokensBefore = counted - ? countTokens(entry.expected.normalized) - : 0; - const tokensAfter = compressed - ? countTokens(entry.expected.encoded as string) - : tokensBefore; - expect(stats).toStrictEqual({ - tokensBefore, - tokensAfter, - costSavings: tokensBefore - tokensAfter, - wasEffective: compressed, - hadToolResults: counted, - }); + test("a lone rejected result: original counted in both totals, wasEffective=false", async () => { + await upsertPricing(); - const [, toolMessage] = adapter.getProviderMessages(); - expect(toolMessage).toStrictEqual({ + const adapter = openaiAdapterFactory.createRequestAdapter({ + model: "gpt-4o", + messages: [ + { role: "user", content: "run the tool" }, + { role: "tool", tool_call_id: "call_1", - content: compressed - ? (entry.expected.encoded as string) - : entry.rawContent, - }); - }); - } + content: NEAR_BOUNDARY.rawContent, + }, + ], + }); + const stats = await adapter.applyToonCompression("gpt-4o"); + + const boundaryTokens = countTokens(NEAR_BOUNDARY.expected.normalized); + expect(stats).toStrictEqual({ + tokensBefore: boundaryTokens, + tokensAfter: boundaryTokens, + costSavings: 0, + wasEffective: false, + hadToolResults: true, + }); + const [, toolMessage] = adapter.getProviderMessages(); + expect(toolMessage).toStrictEqual({ + role: "tool", + tool_call_id: "call_1", + content: NEAR_BOUNDARY.rawContent, + }); }); }); diff --git a/platform/backend/src/routes/proxy/adapters/zhipuai-toon-compression.test.ts b/platform/backend/src/routes/proxy/adapters/zhipuai-toon-compression.test.ts index 8221893824b..700eae7be55 100644 --- a/platform/backend/src/routes/proxy/adapters/zhipuai-toon-compression.test.ts +++ b/platform/backend/src/routes/proxy/adapters/zhipuai-toon-compression.test.ts @@ -1,38 +1,24 @@ // Pins the ZhipuAI adapter's TOON compression cutover to the native addon: // full transformed-request exact equality (TOON content from the committed v3 -// golden corpus, everything else byte-equal) and the exact -// ToolCompressionStats accounting matrix (rejected payloads counted in BOTH -// totals — the OpenAI-family rule). Requires the built addon: mandatory in -// CI; locally it skips visibly — run `pnpm test:native` from platform/backend. - -import { readFileSync } from "node:fs"; -import path from "node:path"; -import { ModelModel } from "@/models"; -import { describe, expect, test } from "@/test"; -import { getTokenizer } from "@/tokenizers"; +// golden corpus, everything else byte-equal) plus the accounting semantics +// the combined request cannot isolate (hadToolResults=false when nothing was +// parseable; a lone rejected payload counted in BOTH totals — the +// OpenAI-family rule). Requires the built addon: mandatory in CI; locally it +// skips visibly — run `pnpm test:native` from platform/backend. + +import { expect, test } from "@/test"; +import { + corpusEntry, + describeNative, + makeCountTokens, + makeToolCall, + upsertOneDollarPerTokenPricing, +} from "@/test/toon-golden"; import type { Zhipuai } from "@/types"; import { zhipuaiAdapterFactory } from "./zhipuai"; type ZhipuaiRequest = Zhipuai.Types.ChatCompletionsRequest; -type GoldenEntry = { - name: string; - rawContent: string; - unwrap: boolean; - expected: { normalized: string; encoded: string | null }; -}; - -const CORPUS_PATH = path.resolve( - import.meta.dirname, - "../../../../../archestra-rs/proxy-transform-core/tests/fixtures/golden-corpus.json", -); -const corpus: GoldenEntry[] = JSON.parse(readFileSync(CORPUS_PATH, "utf8")); -function corpusEntry(name: string): GoldenEntry { - const entry = corpus.find((candidate) => candidate.name === name); - if (!entry) throw new Error(`golden corpus entry not found: ${name}`); - return entry; -} - // Corpus picks: a uniform array (compression wins), a wrapped // [{type:"text",...}] payload (unwrapped, compression wins), malformed JSON // (kept as-is), and a near-boundary object whose TOON encoding does not save @@ -42,54 +28,13 @@ const WRAPPED = corpusEntry("wrapped-single-text"); const MALFORMED = corpusEntry("malformed-prose"); const NEAR_BOUNDARY = corpusEntry("boundary-obj-1"); -const tokenizer = getTokenizer("zhipuai"); -const countTokens = (content: string) => - tokenizer.countTokens([{ role: "user", content }]); - -// $1,000,000 per million input tokens = $1 per token, so expected costSavings -// equals tokens saved exactly. -async function upsertOneDollarPerTokenPricing() { - await ModelModel.upsert({ - externalId: "zhipuai/glm-4.6", - provider: "zhipuai", - modelId: "glm-4.6", - inputModalities: null, - outputModalities: null, - customPricePerMillionInput: "1000000.00", - customPricePerMillionOutput: "1000000.00", - lastSyncedAt: new Date(), - }); -} - -const addonLoadError: unknown = await import( - "@archestra/proxy-transform-rs" -).then( - () => null, - (error) => error, -); - -// In CI the suite must FAIL (never skip) when the addon is missing. -const describeNative = - addonLoadError === null || process.env.CI ? describe : describe.skip; -if (addonLoadError !== null && !process.env.CI) { - console.warn( - `[zhipuai-toon-compression.test] skipping: @archestra/proxy-transform-rs is not built (${String( - addonLoadError, - )}). Run \`pnpm test:native\` from platform/backend.`, - ); -} - -function makeToolCall(id: string, name: string) { - return { - id, - type: "function" as const, - function: { name, arguments: '{"directory":"."}' }, - }; -} +const countTokens = makeCountTokens("zhipuai"); +const upsertPricing = () => + upsertOneDollarPerTokenPricing({ provider: "zhipuai", modelId: "glm-4.6" }); describeNative("ZhipuAI adapter TOON compression (native addon)", () => { test("transforms the full provider request exactly (goldens for TOON, byte-equal elsewhere)", async () => { - await upsertOneDollarPerTokenPricing(); + await upsertPricing(); const makeRequest = (): ZhipuaiRequest => ({ model: "glm-4.6", @@ -168,7 +113,7 @@ describeNative("ZhipuAI adapter TOON compression (native addon)", () => { }); test("applies native results to the right candidates when non-candidate messages are interleaved", async () => { - await upsertOneDollarPerTokenPricing(); + await upsertPricing(); // ZhipuAI tool messages are string-only, so non-candidates here are // assistant/user messages between tool messages: the native result index @@ -234,65 +179,63 @@ describeNative("ZhipuAI adapter TOON compression (native addon)", () => { }); }); - describe("exact ToolCompressionStats accounting matrix", () => { - const rows: { - row: string; - entry: GoldenEntry; - compressed: boolean; - counted: boolean; - }[] = [ - { row: "malformed", entry: MALFORMED, compressed: false, counted: false }, - { - row: "ineffective (rejected: original counted in both totals)", - entry: NEAR_BOUNDARY, - compressed: false, - counted: true, - }, - { row: "effective", entry: UNIFORM, compressed: true, counted: true }, - { - row: "wrapped-array", - entry: WRAPPED, - compressed: true, - counted: true, - }, - ]; + // The two accounting cases the combined request above cannot isolate. + test("a lone malformed result: zeroed stats (hadToolResults=false), message untouched", async () => { + await upsertPricing(); - for (const { row, entry, compressed, counted } of rows) { - test(row, async () => { - await upsertOneDollarPerTokenPricing(); + const adapter = zhipuaiAdapterFactory.createRequestAdapter({ + model: "glm-4.6", + messages: [ + { role: "user", content: "run the tool" }, + { role: "tool", tool_call_id: "call_1", content: MALFORMED.rawContent }, + ], + }); + const stats = await adapter.applyToonCompression("glm-4.6"); - const adapter = zhipuaiAdapterFactory.createRequestAdapter({ - model: "glm-4.6", - messages: [ - { role: "user", content: "run the tool" }, - { role: "tool", tool_call_id: "call_1", content: entry.rawContent }, - ], - }); - const stats = await adapter.applyToonCompression("glm-4.6"); + expect(stats).toStrictEqual({ + tokensBefore: 0, + tokensAfter: 0, + costSavings: 0, + wasEffective: false, + hadToolResults: false, + }); + const [, toolMessage] = adapter.getProviderMessages(); + expect(toolMessage).toStrictEqual({ + role: "tool", + tool_call_id: "call_1", + content: MALFORMED.rawContent, + }); + }); - const tokensBefore = counted - ? countTokens(entry.expected.normalized) - : 0; - const tokensAfter = compressed - ? countTokens(entry.expected.encoded as string) - : tokensBefore; - expect(stats).toStrictEqual({ - tokensBefore, - tokensAfter, - costSavings: tokensBefore - tokensAfter, - wasEffective: compressed, - hadToolResults: counted, - }); + test("a lone rejected result: original counted in both totals, wasEffective=false", async () => { + await upsertPricing(); - const [, toolMessage] = adapter.getProviderMessages(); - expect(toolMessage).toStrictEqual({ + const adapter = zhipuaiAdapterFactory.createRequestAdapter({ + model: "glm-4.6", + messages: [ + { role: "user", content: "run the tool" }, + { role: "tool", tool_call_id: "call_1", - content: compressed - ? (entry.expected.encoded as string) - : entry.rawContent, - }); - }); - } + content: NEAR_BOUNDARY.rawContent, + }, + ], + }); + const stats = await adapter.applyToonCompression("glm-4.6"); + + const boundaryTokens = countTokens(NEAR_BOUNDARY.expected.normalized); + expect(stats).toStrictEqual({ + tokensBefore: boundaryTokens, + tokensAfter: boundaryTokens, + costSavings: 0, + wasEffective: false, + hadToolResults: true, + }); + const [, toolMessage] = adapter.getProviderMessages(); + expect(toolMessage).toStrictEqual({ + role: "tool", + tool_call_id: "call_1", + content: NEAR_BOUNDARY.rawContent, + }); }); }); diff --git a/platform/backend/src/test/toon-golden.ts b/platform/backend/src/test/toon-golden.ts new file mode 100644 index 00000000000..bf8188af0d3 --- /dev/null +++ b/platform/backend/src/test/toon-golden.ts @@ -0,0 +1,80 @@ +// Shared harness for the per-adapter TOON golden suites +// (src/routes/proxy/adapters/*-toon-compression.test.ts): golden-corpus +// loading, the addon-required skip/fail gate, deterministic pricing, and +// per-provider token counting. Adapter-specific semantics stay in the suites. + +import { readFileSync } from "node:fs"; +import path from "node:path"; +import type { SupportedProvider } from "@archestra/shared"; +import { ModelModel } from "@/models"; +import { describe } from "@/test"; +import { getTokenizer } from "@/tokenizers"; + +export type GoldenEntry = { + name: string; + rawContent: string; + unwrap: boolean; + expected: { normalized: string; encoded: string | null }; +}; + +export function corpusEntry(name: string): GoldenEntry { + const entry = corpus.find((candidate) => candidate.name === name); + if (!entry) throw new Error(`golden corpus entry not found: ${name}`); + return entry; +} + +export function makeCountTokens(provider: SupportedProvider) { + const tokenizer = getTokenizer(provider); + return (content: string) => + tokenizer.countTokens([{ role: "user", content }]); +} + +// $1,000,000 per million input tokens = $1 per token, so expected costSavings +// equals tokens saved exactly. +export async function upsertOneDollarPerTokenPricing(params: { + provider: SupportedProvider; + modelId: string; +}) { + await ModelModel.upsert({ + externalId: `${params.provider}/${params.modelId}`, + provider: params.provider, + modelId: params.modelId, + inputModalities: null, + outputModalities: null, + customPricePerMillionInput: "1000000.00", + customPricePerMillionOutput: "1000000.00", + lastSyncedAt: new Date(), + }); +} + +export function makeToolCall(id: string, name: string) { + return { + id, + type: "function" as const, + function: { name, arguments: '{"directory":"."}' }, + }; +} + +const CORPUS_PATH = path.resolve( + import.meta.dirname, + "../../../archestra-rs/proxy-transform-core/tests/fixtures/golden-corpus.json", +); +const corpus: GoldenEntry[] = JSON.parse(readFileSync(CORPUS_PATH, "utf8")); + +const addonLoadError: unknown = await import( + "@archestra/proxy-transform-rs" +).then( + () => null, + (error) => error, +); + +// In CI the golden suites must FAIL (never skip) when the addon is missing. +export const describeNative = + addonLoadError === null || process.env.CI ? describe : describe.skip; +if (addonLoadError !== null && !process.env.CI) { + console.warn( + `[toon golden suites] skipping: @archestra/proxy-transform-rs is not built (${String( + addonLoadError, + )}). Run \`pnpm test:native\` from platform/backend.`, + ); +} From 672b28e2854d7993c26514a1b007b93632559244 Mon Sep 17 00:00:00 2001 From: Arseny Kravchenko Date: Fri, 10 Jul 2026 19:39:06 +0200 Subject: [PATCH 13/18] chore: regenerate openapi/client types after merging main Main's interactions-response changes were generated without the addon_unavailable skip reason added on this branch. Claude-Session: https://claude.ai/code/session_01QkeLJprfeq75d2NY39aFiu --- docs/openapi.json | 6 ++++-- platform/shared/hey-api/clients/api/types.gen.ts | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/openapi.json b/docs/openapi.json index 2afb74ce0c0..2b8b1692007 100644 --- a/docs/openapi.json +++ b/docs/openapi.json @@ -130479,7 +130479,8 @@ "enum": [ "not_enabled", "not_effective", - "no_tool_results" + "no_tool_results", + "addon_unavailable" ] }, "createdAt": { @@ -152156,7 +152157,8 @@ "enum": [ "not_enabled", "not_effective", - "no_tool_results" + "no_tool_results", + "addon_unavailable" ] }, "createdAt": { diff --git a/platform/shared/hey-api/clients/api/types.gen.ts b/platform/shared/hey-api/clients/api/types.gen.ts index 74ba82b6e81..618ff158a2d 100644 --- a/platform/shared/hey-api/clients/api/types.gen.ts +++ b/platform/shared/hey-api/clients/api/types.gen.ts @@ -35659,7 +35659,7 @@ export type GetInteractionsResponses = { toonTokensBefore: number | null; toonTokensAfter: number | null; toonCostSavings: string | null; - toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results'; + toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results' | 'addon_unavailable'; createdAt: string; chatErrors?: Array<{ id: string; @@ -40101,7 +40101,7 @@ export type GetInteractionResponses = { toonTokensBefore: number | null; toonTokensAfter: number | null; toonCostSavings: string | null; - toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results'; + toonSkipReason?: 'not_enabled' | 'not_effective' | 'no_tool_results' | 'addon_unavailable'; createdAt: string; chatErrors?: Array<{ id: string; From 1e5a7ba2957714f1aa0acd4def9618c6d7164772 Mon Sep 17 00:00:00 2001 From: Arseny Kravchenko Date: Fri, 10 Jul 2026 21:05:55 +0200 Subject: [PATCH 14/18] docs: tighten TOON compression wording Claude-Session: https://claude.ai/code/session_01QkeLJprfeq75d2NY39aFiu --- docs/pages/platform-adding-llm-providers.md | 6 +++--- docs/pages/platform-costs-and-limits.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/pages/platform-adding-llm-providers.md b/docs/pages/platform-adding-llm-providers.md index df141f223ac..caf0448907a 100644 --- a/docs/pages/platform-adding-llm-providers.md +++ b/docs/pages/platform-adding-llm-providers.md @@ -171,11 +171,11 @@ The function must: 1. Iterate through provider-specific message array structure 2. Find tool result messages (e.g., `role: "tool"` in OpenAI, `tool_result` blocks in Anthropic, `functionResponse` parts in Gemini) -3. Encode the extracted results in one batch with the shared helper `toonEncodeToolResults()` from `backend/src/routes/proxy/utils/toon-native.ts` — it runs unwrap, JSON parse, and TOON encode in the native `@archestra/proxy-transform-rs` addon. Do not encode TOON in the adapter itself -4. Decide keep or reject per result in the adapter: count tokens with the provider tokenizer and keep the TOON version only when it saves tokens +3. Batch the extracted results through `toonEncodeToolResults()` from `backend/src/routes/proxy/utils/toon-native.ts`. The native addon unwraps, parses, and encodes them — never encode TOON in the adapter +4. Count tokens with the provider tokenizer; keep the TOON version only when it saves tokens 5. Return compressed messages and compression statistics -The helper fails open. When the native addon is unavailable it returns `null`; the adapter must then keep the original tool results and report the `addon_unavailable` skip reason. +`toonEncodeToolResults()` returns `null` when the native addon is unavailable. The adapter must then keep the original results and report the `addon_unavailable` skip reason. ### Metrics diff --git a/docs/pages/platform-costs-and-limits.md b/docs/pages/platform-costs-and-limits.md index 9e3f0514e73..fd11503b166 100644 --- a/docs/pages/platform-costs-and-limits.md +++ b/docs/pages/platform-costs-and-limits.md @@ -99,7 +99,7 @@ Compression is skipped when: - TOON is disabled - a response has no tool results - the TOON version would not save tokens -- the compression engine is unavailable (`addon_unavailable`) — an infrastructure failure; the request proceeds with uncompressed tool results +- the compression engine is unavailable (reported as `addon_unavailable`) Archestra records before/after token counts and savings when compression is applied, so those savings appear in logs and aggregate cost reporting. From 61d15ceb9a6789e0d668eec59ac6bc7b3cf959f9 Mon Sep 17 00:00:00 2001 From: Arseny Kravchenko Date: Fri, 10 Jul 2026 23:29:55 +0200 Subject: [PATCH 15/18] feat(proxy-transform): fuse cl100k token counting into the native kernel The TOON keep/reject decision counts tokens with a synchronous WASM tiktoken call on the Node event loop, twice per candidate. Under concurrency that blocks the loop (measured: 0 -> ~260ms p99 event-loop delay at 8-way), spiking every other request's latency, while the encode already runs off-thread. Add cl100k_base counting (tiktoken-rs, byte-identical ranks) to the core, fused into the same off-thread encode pass. The batch-level BeforeSource option selects it: Normalized for most adapters, Raw for Gemini (which tokenizes its pre-unwrap serialization), None to skip (Anthropic/Bedrock keep their own tokenizer). Counts populate beforeTokens/encodedTokens only for encodable items, matching every adapter. Rust-only slice; the TypeScript adapters still pass one arg (optional param) until they cut over. Claude-Session: https://claude.ai/code/session_01QkeLJprfeq75d2NY39aFiu --- platform/archestra-rs/Cargo.lock | 71 ++++++- .../proxy-transform-core/Cargo.toml | 4 + .../benches/toon_kernel.rs | 33 ++-- .../proxy-transform-core/src/lib.rs | 174 ++++++++++++++++-- .../proxy-transform-core/src/tokenize.rs | 67 +++++++ .../tests/golden_corpus.rs | 2 +- .../tests/roundtrip_property.rs | 37 ++-- .../proxy-transform-rs/index.d.ts | 37 +++- .../proxy-transform-rs/src/lib.rs | 19 +- 9 files changed, 388 insertions(+), 56 deletions(-) create mode 100644 platform/archestra-rs/proxy-transform-core/src/tokenize.rs diff --git a/platform/archestra-rs/Cargo.lock b/platform/archestra-rs/Cargo.lock index a8c5710d345..d4424296023 100644 --- a/platform/archestra-rs/Cargo.lock +++ b/platform/archestra-rs/Cargo.lock @@ -113,15 +113,30 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec 0.6.3", +] + [[package]] name = "bit-set" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" dependencies = [ - "bit-vec", + "bit-vec 0.8.0", ] +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + [[package]] name = "bit-vec" version = "0.8.0" @@ -149,6 +164,17 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bstr" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cee35f73844aa3014bb606320a6c1f010249dbdf43342fe54b5a4f6a8ed4b79" +dependencies = [ + "memchr", + "regex-automata", + "serde_core", +] + [[package]] name = "bumpalo" version = "3.20.3" @@ -607,6 +633,17 @@ dependencies = [ "once_cell", ] +[[package]] +name = "fancy-regex" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "531e46835a22af56d1e3b66f04844bed63158bc094a628bec1d321d9b4c44bf2" +dependencies = [ + "bit-set 0.5.3", + "regex-automata", + "regex-syntax", +] + [[package]] name = "fastrand" version = "2.4.1" @@ -1502,7 +1539,7 @@ dependencies = [ "napi-build", "napi-sys", "nohash-hasher", - "rustc-hash", + "rustc-hash 2.1.2", "tokio", ] @@ -1811,8 +1848,8 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" dependencies = [ - "bit-set", - "bit-vec", + "bit-set 0.8.0", + "bit-vec 0.8.0", "bitflags 2.13.0", "num-traits", "rand", @@ -1858,6 +1895,7 @@ dependencies = [ "proptest", "serde", "serde_json", + "tiktoken-rs", "toon-format", ] @@ -1901,7 +1939,7 @@ dependencies = [ "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash", + "rustc-hash 2.1.2", "rustls 0.23.41", "socket2 0.6.4", "thiserror 2.0.18", @@ -1922,7 +1960,7 @@ dependencies = [ "lru-slab", "rand", "ring", - "rustc-hash", + "rustc-hash 2.1.2", "rustls 0.23.41", "rustls-pki-types", "slab", @@ -2169,6 +2207,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc-hash" version = "2.1.2" @@ -2718,6 +2762,21 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "tiktoken-rs" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25563eeba904d770acf527e8b370fe9a5547bacd20ff84a0b6c3bc41288e5625" +dependencies = [ + "anyhow", + "base64 0.22.1", + "bstr", + "fancy-regex", + "lazy_static", + "regex", + "rustc-hash 1.1.0", +] + [[package]] name = "tinystr" version = "0.8.3" diff --git a/platform/archestra-rs/proxy-transform-core/Cargo.toml b/platform/archestra-rs/proxy-transform-core/Cargo.toml index 3d3a0bce050..a78f16a4598 100644 --- a/platform/archestra-rs/proxy-transform-core/Cargo.toml +++ b/platform/archestra-rs/proxy-transform-core/Cargo.toml @@ -15,6 +15,10 @@ napi-derive = { version = "3", optional = true } # de traits for the borrowed JSON DOM (src/json.rs); derive only in tests serde = "1" serde_json = "1" +# cl100k_base tokenizer for the fused keep/reject token counting (src/tokenize.rs), +# byte-identical ranks to the JS `tiktoken` get_encoding("cl100k_base") the +# adapters used. Pinned exact — the rank table is the parity contract. +tiktoken-rs = "=0.7.0" [dev-dependencies] criterion = "0.5" diff --git a/platform/archestra-rs/proxy-transform-core/benches/toon_kernel.rs b/platform/archestra-rs/proxy-transform-core/benches/toon_kernel.rs index 9e85297cb03..abd98e085ef 100644 --- a/platform/archestra-rs/proxy-transform-core/benches/toon_kernel.rs +++ b/platform/archestra-rs/proxy-transform-core/benches/toon_kernel.rs @@ -8,7 +8,7 @@ use criterion::{ BatchSize, BenchmarkId, Criterion, Throughput, black_box, criterion_group, criterion_main, }; -use proxy_transform_core::{ToonEncodeItem, toon_encode_tool_results}; +use proxy_transform_core::{BeforeSource, ToonEncodeItem, toon_encode_tool_results}; use serde_json::{Value, json}; /// (label, payload bytes) per corpus; every corpus is a 10-item batch covering @@ -128,17 +128,26 @@ fn build_payload(kind: PayloadKind, target_bytes: usize, rng: &mut TinyRng) -> S fn bench_toon_kernel(c: &mut Criterion) { let mut group = c.benchmark_group("toon_kernel"); group.sample_size(20); - for (label, payload_bytes) in PAYLOAD_SIZES { - let batch = build_batch(payload_bytes, 0x5eed ^ payload_bytes as u64); - let total_bytes: usize = batch.iter().map(|item| item.raw_content.len()).sum(); - group.throughput(Throughput::Bytes(total_bytes as u64)); - group.bench_with_input(BenchmarkId::from_parameter(label), &batch, |b, batch| { - b.iter_batched( - || batch.clone(), - |items| black_box(toon_encode_tool_results(black_box(items))), - BatchSize::LargeInput, - ); - }); + // Two variants per size: `encode` isolates the transform; `encode+count` + // adds the fused cl100k token counting. The delta is the native marginal + // cost of the counting that used to run as synchronous WASM calls on the + // Node event loop. + for (variant, count) in [ + ("encode", None), + ("encode+count", Some(BeforeSource::Normalized)), + ] { + for (label, payload_bytes) in PAYLOAD_SIZES { + let batch = build_batch(payload_bytes, 0x5eed ^ payload_bytes as u64); + let total_bytes: usize = batch.iter().map(|item| item.raw_content.len()).sum(); + group.throughput(Throughput::Bytes(total_bytes as u64)); + group.bench_with_input(BenchmarkId::new(variant, label), &batch, |b, batch| { + b.iter_batched( + || batch.clone(), + |items| black_box(toon_encode_tool_results(black_box(items), count)), + BatchSize::LargeInput, + ); + }); + } } group.finish(); } diff --git a/platform/archestra-rs/proxy-transform-core/src/lib.rs b/platform/archestra-rs/proxy-transform-core/src/lib.rs index 659ac463705..b19ec91edc2 100644 --- a/platform/archestra-rs/proxy-transform-core/src/lib.rs +++ b/platform/archestra-rs/proxy-transform-core/src/lib.rs @@ -14,6 +14,7 @@ mod encode; mod json; +mod tokenize; use json::JsonValue; @@ -29,6 +30,21 @@ pub struct ToonEncodeItem { pub unwrap: bool, } +/// Which string an adapter tokenizes as the pre-compression baseline, selecting +/// the fused token counting (see [`toon_encode_tool_results`]). Passing `None` +/// there skips counting entirely (Anthropic and Bedrock keep their own JS +/// tokenizer); the two variants below cover the tiktoken-family adapters. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "napi", napi_derive::napi(string_enum))] +pub enum BeforeSource { + /// Count the original `raw_content` (pre-unwrap) as the baseline — Gemini, + /// which tokenizes its serialized response, not the unwrapped inner text. + Raw, + /// Count the `normalized` (post-unwrap) content — every other tiktoken + /// adapter (OpenAI family, Cohere, ZhipuAI, MiniMax). + Normalized, +} + /// The transformation output for one item. `normalized` is the unwrapped string /// when unwrapping was requested and matched, else the original `raw_content` /// (adapters tokenize it for accounting). `encoded` is the TOON encoding, or @@ -36,13 +52,22 @@ pub struct ToonEncodeItem { /// exceed the anti-amplification output budget (see `encode` module docs); /// adapters keep the original payload either way. /// -/// `use_nullable` makes `encoded: None` cross the boundary as an explicit JS -/// `null` (typed `string | null`) instead of an omitted key. +/// `before_tokens`/`encoded_tokens` are the cl100k counts of the baseline and +/// the compressed candidate, populated only when a [`BeforeSource`] is passed +/// AND `encoded` is `Some` (an unencodable item is never tokenized, matching +/// the adapters). Both `None` otherwise. +/// +/// `use_nullable` makes `None` fields cross the boundary as an explicit JS +/// `null` (typed `string | null` / `number | null`) instead of an omitted key. #[derive(Clone, Debug)] #[cfg_attr(feature = "napi", napi_derive::napi(object, use_nullable = true))] pub struct ToonEncodeResult { pub normalized: String, pub encoded: Option, + #[cfg_attr(feature = "napi", napi(js_name = "beforeTokens"))] + pub before_tokens: Option, + #[cfg_attr(feature = "napi", napi(js_name = "encodedTokens"))] + pub encoded_tokens: Option, } /// Items whose raw content exceeds this many bytes are not parsed or encoded @@ -63,7 +88,16 @@ pub const MAX_ITEM_INPUT_BYTES: usize = 10 * 1024 * 1024; /// total produced output is capped at `2 x total input bytes + one floor`; /// once the running total exceeds it, remaining items are not encoded /// (`encoded: None`, fail-open like the per-item budget, unwrap still applied). -pub fn toon_encode_tool_results(items: Vec) -> Vec { +/// +/// When `count` is `Some`, the cl100k token counts that gate each adapter's +/// keep/reject decision are computed here, in the same off-thread pass, instead +/// of by a synchronous WASM tokenizer on the Node event loop. `None` leaves +/// `before_tokens`/`encoded_tokens` unset (Anthropic/Bedrock count with their +/// own tokenizer). +pub fn toon_encode_tool_results( + items: Vec, + count: Option, +) -> Vec { let total_input: usize = items .iter() .map(|item| item.raw_content.len()) @@ -75,7 +109,7 @@ pub fn toon_encode_tool_results(items: Vec) -> Vec ToonEncodeResult { +fn encode_item( + item: ToonEncodeItem, + encode_enabled: bool, + count: Option, +) -> ToonEncodeResult { // Input size cap: see MAX_ITEM_INPUT_BYTES. Checked before any parse. if item.raw_content.len() > MAX_ITEM_INPUT_BYTES { return ToonEncodeResult { normalized: item.raw_content, encoded: None, + before_tokens: None, + encoded_tokens: None, }; } // Single DOM parse per item: the unwrap check reuses the parsed value @@ -125,10 +165,22 @@ fn encode_item(item: ToonEncodeItem, encode_enabled: bool) -> ToonEncodeResult { } }; match outcome { - ItemOutcome::Encoded(encoded) => ToonEncodeResult { - normalized: item.raw_content, - encoded, - }, + ItemOutcome::Encoded(encoded) => { + // normalized == raw_content on this path, so Raw and Normalized + // select the same string. + let (before_tokens, encoded_tokens) = counts( + count, + &item.raw_content, + &item.raw_content, + encoded.as_deref(), + ); + ToonEncodeResult { + normalized: item.raw_content, + encoded, + before_tokens, + encoded_tokens, + } + } ItemOutcome::Unwrapped(text) => { let encoded = if encode_enabled { json::parse_json(&text) @@ -136,14 +188,45 @@ fn encode_item(item: ToonEncodeItem, encode_enabled: bool) -> ToonEncodeResult { } else { None }; + let (before_tokens, encoded_tokens) = + counts(count, &item.raw_content, &text, encoded.as_deref()); ToonEncodeResult { normalized: text, encoded, + before_tokens, + encoded_tokens, } } } } +/// cl100k token counts for the keep/reject decision. Computed only for +/// encodable items (`encoded` is `Some`) — every adapter skips tokenizing a +/// result it cannot compress, so counting one here would be wasted work and a +/// meaningless "before". `before` follows the adapter's semantics via +/// [`BeforeSource`]. A `None` from the tokenizer (encoder init failed) is +/// propagated so the binding can fail open rather than report a bogus zero. +fn counts( + count: Option, + raw: &str, + normalized: &str, + encoded: Option<&str>, +) -> (Option, Option) { + match (count, encoded) { + (Some(source), Some(encoded)) => { + let before = match source { + BeforeSource::Raw => raw, + BeforeSource::Normalized => normalized, + }; + ( + tokenize::count_user_tokens(before), + tokenize::count_user_tokens(encoded), + ) + } + _ => (None, None), + } +} + fn encode_value(value: &JsonValue<'_>, input_len: usize, enabled: bool) -> Option { if !enabled { return None; @@ -191,11 +274,14 @@ mod tests { use super::*; fn encode_one(raw_content: &str, unwrap: bool) -> ToonEncodeResult { - let results = toon_encode_tool_results(vec![ToonEncodeItem { - id: "t1".to_string(), - raw_content: raw_content.to_string(), - unwrap, - }]); + let results = toon_encode_tool_results( + vec![ToonEncodeItem { + id: "t1".to_string(), + raw_content: raw_content.to_string(), + unwrap, + }], + None, + ); assert_eq!(results.len(), 1); results.into_iter().next().expect("one result") } @@ -374,7 +460,7 @@ mod tests { let total_input = raw.len() * 40; let batch_budget = 2 * total_input + 16 * 1024; - let results = toon_encode_tool_results(items); + let results = toon_encode_tool_results(items, None); assert_eq!(results.len(), 40); let encoded_count = results.iter().filter(|r| r.encoded.is_some()).count(); assert_eq!(encoded_count, 3, "first items encode, tail is skipped"); @@ -442,7 +528,7 @@ mod tests { unwrap: false, }, ]; - let results = toon_encode_tool_results(items); + let results = toon_encode_tool_results(items, None); assert_eq!(results.len(), 3); assert_eq!(results[0].encoded.as_deref(), Some("a: 1")); assert_eq!(results[1].encoded, None); @@ -451,6 +537,60 @@ mod tests { #[test] fn empty_batch_returns_empty() { - assert!(toon_encode_tool_results(Vec::new()).is_empty()); + assert!(toon_encode_tool_results(Vec::new(), None).is_empty()); + } + + fn count_one(raw_content: &str, unwrap: bool, count: BeforeSource) -> ToonEncodeResult { + let results = toon_encode_tool_results( + vec![ToonEncodeItem { + id: "t1".to_string(), + raw_content: raw_content.to_string(), + unwrap, + }], + Some(count), + ); + results.into_iter().next().expect("one result") + } + + #[test] + fn no_count_leaves_token_fields_unset() { + let result = encode_one(INNER, true); + assert!(result.encoded.is_some()); + assert_eq!(result.before_tokens, None); + assert_eq!(result.encoded_tokens, None); + } + + #[test] + fn count_populates_both_token_fields_for_encoded_items() { + let result = count_one(INNER, false, BeforeSource::Normalized); + let before = result.before_tokens.expect("before counted"); + let after = result.encoded_tokens.expect("encoded counted"); + assert!(before > 0 && after > 0); + // The whole point: the TOON encoding of a uniform object is smaller. + assert!(after < before, "before={before} after={after}"); + } + + #[test] + fn count_skips_unencodable_items() { + // Not JSON -> encoded None -> no "before" to compare, nothing counted. + let result = count_one("plain prose, not json", true, BeforeSource::Normalized); + assert_eq!(result.encoded, None); + assert_eq!(result.before_tokens, None); + assert_eq!(result.encoded_tokens, None); + } + + #[test] + fn before_source_selects_raw_vs_normalized_when_they_differ() { + // A wrapped payload: normalized (inner INNER) differs from raw (the + // whole wrapper array). Raw counts more tokens than Normalized. + let wrapped = serde_json::to_string(&serde_json::json!([{"type": "text", "text": INNER}])) + .expect("serialize fixture"); + let normalized = count_one(&wrapped, true, BeforeSource::Normalized) + .before_tokens + .expect("normalized before"); + let raw = count_one(&wrapped, true, BeforeSource::Raw) + .before_tokens + .expect("raw before"); + assert!(raw > normalized, "raw={raw} normalized={normalized}"); } } diff --git a/platform/archestra-rs/proxy-transform-core/src/tokenize.rs b/platform/archestra-rs/proxy-transform-core/src/tokenize.rs new file mode 100644 index 00000000000..f0a42bd6894 --- /dev/null +++ b/platform/archestra-rs/proxy-transform-core/src/tokenize.rs @@ -0,0 +1,67 @@ +//! cl100k_base token counting, fused into the encode pass so the LLM proxy's +//! TOON keep/reject decision no longer runs a synchronous WASM tokenizer on the +//! Node event loop (it blocks concurrent requests; the encode already runs +//! off-thread). Byte-identical to the JS path in +//! `backend/src/tokenizers/{tiktoken,base}.ts`. + +use std::sync::LazyLock; + +use tiktoken_rs::CoreBPE; + +/// The JS tokenizer prepends the message role to the content before encoding +/// (`backend/src/tokenizers/base.ts` `getEncodableText`: `${role}${text}`), and +/// all five tiktoken-family TOON adapters count under role `"user"`. Matching +/// that prefix is part of the parity contract enforced by the Node differential +/// test; a divergence here silently shifts recorded token counts. +const ROLE_PREFIX: &str = "user"; + +/// Process-wide cl100k_base encoder. Construction parses a vendored rank table; +/// a failure yields `None` and counting is reported as unavailable rather than +/// panicking — the core promises never to panic (the adapters then fail open to +/// the `addon_unavailable` skip reason, exactly like a missing addon). +static CL100K: LazyLock> = LazyLock::new(|| tiktoken_rs::cl100k_base().ok()); + +/// Count cl100k tokens of `role + text`, matching JS +/// `countTokens([{ role: "user", content: text }])`. `None` only when the +/// encoder failed to initialize. Uses ordinary encoding: special-token literals +/// (e.g. `<|endoftext|>`) in tool results are counted as plain text instead of +/// raising, matching the ordinary-encoding baseline the JS tokenizer now uses. +pub(crate) fn count_user_tokens(text: &str) -> Option { + let bpe = CL100K.as_ref()?; + let mut prefixed = String::with_capacity(ROLE_PREFIX.len() + text.len()); + prefixed.push_str(ROLE_PREFIX); + prefixed.push_str(text); + Some(bpe.encode_ordinary(&prefixed).len() as u32) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn counts_are_available_and_positive() { + assert!(count_user_tokens("hello world").is_some_and(|n| n > 0)); + } + + #[test] + fn empty_text_counts_the_role_prefix_only() { + // "user" alone is one cl100k token; the count is deterministic and the + // role prefix is always present. + assert_eq!(count_user_tokens(""), count_user_tokens("")); + assert!(count_user_tokens("").is_some_and(|n| n >= 1)); + } + + #[test] + fn special_token_literals_do_not_raise() { + // Ordinary encoding: the reserved marker is counted as text, not an + // error (the JS baseline encodes ordinally too). + assert!(count_user_tokens("<|endoftext|> in a tool result").is_some_and(|n| n > 0)); + } + + #[test] + fn longer_text_counts_more() { + let short = count_user_tokens("a").expect("bpe"); + let long = count_user_tokens(&"lorem ipsum dolor sit amet ".repeat(20)).expect("bpe"); + assert!(long > short); + } +} diff --git a/platform/archestra-rs/proxy-transform-core/tests/golden_corpus.rs b/platform/archestra-rs/proxy-transform-core/tests/golden_corpus.rs index 6e31e7d19e6..41fbfe7b7d9 100644 --- a/platform/archestra-rs/proxy-transform-core/tests/golden_corpus.rs +++ b/platform/archestra-rs/proxy-transform-core/tests/golden_corpus.rs @@ -59,7 +59,7 @@ fn golden_corpus_conformance() { unwrap: case.unwrap, }) .collect(); - let results: Vec = toon_encode_tool_results(items); + let results: Vec = toon_encode_tool_results(items, None); assert_eq!(results.len(), cases.len(), "positional contract"); if std::env::var("UPDATE_TOON_GOLDENS").as_deref() == Ok("1") { diff --git a/platform/archestra-rs/proxy-transform-core/tests/roundtrip_property.rs b/platform/archestra-rs/proxy-transform-core/tests/roundtrip_property.rs index 7e4b6c404a9..787dfed8abe 100644 --- a/platform/archestra-rs/proxy-transform-core/tests/roundtrip_property.rs +++ b/platform/archestra-rs/proxy-transform-core/tests/roundtrip_property.rs @@ -198,11 +198,14 @@ proptest! { let parsed: Value = serde_json::from_str(&raw).expect("reparse generated document"); let raw_len = raw.len(); let budget = (2 * raw_len).max(16 * 1024); - let results = toon_encode_tool_results(vec![ToonEncodeItem { - id: "diff".to_string(), - raw_content: raw, - unwrap: false, - }]); + let results = toon_encode_tool_results( + vec![ToonEncodeItem { + id: "diff".to_string(), + raw_content: raw, + unwrap: false, + }], + None, + ); let expected = toon_format::encode_default(&parsed).ok(); match (&results[0].encoded, &expected) { (None, Some(crate_output)) => prop_assert!( @@ -228,11 +231,14 @@ proptest! { fn encode_decode_roundtrips(value in arb_json()) { let raw = serde_json::to_string(&value).expect("serialize generated value"); let parsed: Value = serde_json::from_str(&raw).expect("reparse generated document"); - let results = toon_encode_tool_results(vec![ToonEncodeItem { - id: "prop".to_string(), - raw_content: raw, - unwrap: false, - }]); + let results = toon_encode_tool_results( + vec![ToonEncodeItem { + id: "prop".to_string(), + raw_content: raw, + unwrap: false, + }], + None, + ); let encoded = results[0].encoded.as_ref().expect("valid JSON always encodes"); let decoded: Value = toon_format::decode_default(encoded) .unwrap_or_else(|error| panic!("decode failed: {error}\nencoded:\n{encoded}")); @@ -251,10 +257,13 @@ proptest! { {"type": "text", "text": inner} ])) .expect("serialize wrapper"); - let results = toon_encode_tool_results(vec![ - ToonEncodeItem { id: "direct".to_string(), raw_content: inner.clone(), unwrap: false }, - ToonEncodeItem { id: "wrapped".to_string(), raw_content: wrapped, unwrap: true }, - ]); + let results = toon_encode_tool_results( + vec![ + ToonEncodeItem { id: "direct".to_string(), raw_content: inner.clone(), unwrap: false }, + ToonEncodeItem { id: "wrapped".to_string(), raw_content: wrapped, unwrap: true }, + ], + None, + ); prop_assert_eq!(&results[1].normalized, &inner); prop_assert_eq!(&results[1].encoded, &results[0].encoded); } diff --git a/platform/archestra-rs/proxy-transform-rs/index.d.ts b/platform/archestra-rs/proxy-transform-rs/index.d.ts index 0f155a7fd29..f21dab47613 100644 --- a/platform/archestra-rs/proxy-transform-rs/index.d.ts +++ b/platform/archestra-rs/proxy-transform-rs/index.d.ts @@ -1,5 +1,24 @@ /* auto-generated by NAPI-RS */ /* eslint-disable */ +/** + * Which string an adapter tokenizes as the pre-compression baseline, selecting + * the fused token counting (see [`toon_encode_tool_results`]). Passing `None` + * there skips counting entirely (Anthropic and Bedrock keep their own JS + * tokenizer); the two variants below cover the tiktoken-family adapters. + */ +export declare const enum BeforeSource { + /** + * Count the original `raw_content` (pre-unwrap) as the baseline — Gemini, + * which tokenizes its serialized response, not the unwrapped inner text. + */ + Raw = 'Raw', + /** + * Count the `normalized` (post-unwrap) content — every other tiktoken + * adapter (OpenAI family, Cohere, ZhipuAI, MiniMax). + */ + Normalized = 'Normalized' +} + /** * One tool result to transform. `id` is the provider tool id, carried for * logging only — it is not unique across items (Anthropic reuses one @@ -19,12 +38,19 @@ export interface ToonEncodeItem { * exceed the anti-amplification output budget (see `encode` module docs); * adapters keep the original payload either way. * - * `use_nullable` makes `encoded: None` cross the boundary as an explicit JS - * `null` (typed `string | null`) instead of an omitted key. + * `before_tokens`/`encoded_tokens` are the cl100k counts of the baseline and + * the compressed candidate, populated only when a [`BeforeSource`] is passed + * AND `encoded` is `Some` (an unencodable item is never tokenized, matching + * the adapters). Both `None` otherwise. + * + * `use_nullable` makes `None` fields cross the boundary as an explicit JS + * `null` (typed `string | null` / `number | null`) instead of an omitted key. */ export interface ToonEncodeResult { normalized: string encoded: string | null + beforeTokens: number | null + encodedTokens: number | null } /** * Transform a batch of tool results off the JS thread: optionally unwrap the @@ -32,5 +58,10 @@ export interface ToonEncodeResult { * as TOON (spec v3). Results are positional — same length and order as * `items`; content that is not parseable JSON yields `encoded: null` (the * caller keeps the original payload). + * + * `beforeSource` selects the fused cl100k token counting: pass it to have the + * off-thread pass populate `beforeTokens`/`encodedTokens` (`Normalized` for + * most adapters, `Raw` for Gemini), or omit it to skip counting (Anthropic and + * Bedrock tokenize with their own tokenizer). */ -export declare function toonEncodeToolResults(items: Array): Promise> +export declare function toonEncodeToolResults(items: Array, beforeSource?: BeforeSource | undefined | null): Promise> diff --git a/platform/archestra-rs/proxy-transform-rs/src/lib.rs b/platform/archestra-rs/proxy-transform-rs/src/lib.rs index 91a7f46facf..8ff453b70fe 100644 --- a/platform/archestra-rs/proxy-transform-rs/src/lib.rs +++ b/platform/archestra-rs/proxy-transform-rs/src/lib.rs @@ -20,6 +20,7 @@ use proxy_transform_core as core; /// runs, so nothing here touches a JS handle. pub struct ToonEncodeTask { items: Vec, + count: Option, } impl Task for ToonEncodeTask { @@ -28,8 +29,9 @@ impl Task for ToonEncodeTask { fn compute(&mut self) -> napi::Result { let items = std::mem::take(&mut self.items); + let count = self.count; std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - core::toon_encode_tool_results(items) + core::toon_encode_tool_results(items, count) })) .map_err(panic_to_napi_error) } @@ -44,12 +46,23 @@ impl Task for ToonEncodeTask { /// as TOON (spec v3). Results are positional — same length and order as /// `items`; content that is not parseable JSON yields `encoded: null` (the /// caller keeps the original payload). +/// +/// `beforeSource` selects the fused cl100k token counting: pass it to have the +/// off-thread pass populate `beforeTokens`/`encodedTokens` (`Normalized` for +/// most adapters, `Raw` for Gemini), or omit it to skip counting (Anthropic and +/// Bedrock tokenize with their own tokenizer). #[napi( js_name = "toonEncodeToolResults", ts_return_type = "Promise>" )] -pub fn toon_encode_tool_results(items: Vec) -> AsyncTask { - AsyncTask::new(ToonEncodeTask { items }) +pub fn toon_encode_tool_results( + items: Vec, + before_source: Option, +) -> AsyncTask { + AsyncTask::new(ToonEncodeTask { + items, + count: before_source, + }) } fn panic_to_napi_error(payload: Box) -> napi::Error { From 6a0badf5e522a1d542da6b60a23bd9ceedf40271 Mon Sep 17 00:00:00 2001 From: Arseny Kravchenko Date: Fri, 10 Jul 2026 23:49:33 +0200 Subject: [PATCH 16/18] feat(proxy): count TOON keep/reject tokens off the event loop Cut the five tiktoken-family adapters (openai family, gemini, cohere, zhipuai, minimax) over to the native fused counts: they pass a before-source to toonEncodeToolResults and read beforeTokens/encodedTokens instead of running a synchronous WASM tokenizer per candidate. gemini uses the raw (pre-unwrap) baseline; the rest use normalized. Anthropic and Bedrock keep their own tokenizer (no cl100k Rust equivalent). Switch the JS tiktoken baseline to encode_ordinary so reserved-marker literals (e.g. <|endoftext|>) in tool results count as text instead of throwing, matching the native path; parity is then exact. The wrapper validates the count invariant at the boundary (present iff requested and encodable, else fail open). Measured under 8-way concurrency: event-loop p99 257ms -> 11ms, wall 6.3x faster; +~30MB RSS for the Rust cl100k table. Claude-Session: https://claude.ai/code/session_01QkeLJprfeq75d2NY39aFiu --- .../proxy-transform-rs/smoke.esm.test.mjs | 11 +- .../proxy-transform-rs/smoke.test.cjs | 23 +- .../proxy/__bench__/bench-token-count.ts | 261 ++++++++++++++++++ .../src/routes/proxy/adapters/cohere.ts | 28 +- .../src/routes/proxy/adapters/gemini.ts | 28 +- .../src/routes/proxy/adapters/minimax.ts | 26 +- .../src/routes/proxy/adapters/openai.ts | 28 +- ...toon-compression-addon-unavailable.test.ts | 58 ++-- .../src/routes/proxy/adapters/zhipuai.ts | 27 +- .../utils/toon-native-token-parity.test.ts | 79 ++++++ .../routes/proxy/utils/toon-native.test.ts | 100 ++++++- .../src/routes/proxy/utils/toon-native.ts | 60 +++- platform/backend/src/test/toon-golden.ts | 5 + platform/backend/src/tokenizers/tiktoken.ts | 7 +- .../backend/src/tokenizers/tokenizers.test.ts | 15 + 15 files changed, 671 insertions(+), 85 deletions(-) create mode 100644 platform/backend/src/routes/proxy/__bench__/bench-token-count.ts create mode 100644 platform/backend/src/routes/proxy/utils/toon-native-token-parity.test.ts diff --git a/platform/archestra-rs/proxy-transform-rs/smoke.esm.test.mjs b/platform/archestra-rs/proxy-transform-rs/smoke.esm.test.mjs index 11ee76cf36f..c378b10fb57 100644 --- a/platform/archestra-rs/proxy-transform-rs/smoke.esm.test.mjs +++ b/platform/archestra-rs/proxy-transform-rs/smoke.esm.test.mjs @@ -9,10 +9,13 @@ const { toonEncodeToolResults } = await import("./index.cjs"); assert.equal(typeof toonEncodeToolResults, "function"); // The async binding is reachable via ESM interop and resolves positionally -// (null encoding for non-JSON content). -const results = await toonEncodeToolResults([ - { id: "esm", rawContent: "not json", unwrap: true }, +// (null encoding and counts for non-JSON content). +const results = await toonEncodeToolResults( + [{ id: "esm", rawContent: "not json", unwrap: true }], + "Normalized", +); +assert.deepEqual(results, [ + { normalized: "not json", encoded: null, beforeTokens: null, encodedTokens: null }, ]); -assert.deepEqual(results, [{ normalized: "not json", encoded: null }]); console.log("proxy-transform-rs esm smoke ok"); diff --git a/platform/archestra-rs/proxy-transform-rs/smoke.test.cjs b/platform/archestra-rs/proxy-transform-rs/smoke.test.cjs index 64dfd57547e..fa23853fd29 100644 --- a/platform/archestra-rs/proxy-transform-rs/smoke.test.cjs +++ b/platform/archestra-rs/proxy-transform-rs/smoke.test.cjs @@ -22,12 +22,31 @@ const items = [ const results = await proxyTransform.toonEncodeToolResults(items); assert.equal(results.length, items.length); - assert.deepEqual(results[0], { normalized: '{"a":1}', encoded: "a: 1" }); + // No before-source: token counts are unset. + assert.deepEqual(results[0], { + normalized: '{"a":1}', + encoded: "a: 1", + beforeTokens: null, + encodedTokens: null, + }); assert.equal(results[1].normalized, inner); assert.equal(results[1].encoded, "data[2]{id,v}:\n 1,a\n 2,b\nok: true"); - assert.deepEqual(results[2], { normalized: "not json at all", encoded: null }); + assert.deepEqual(results[2], { + normalized: "not json at all", + encoded: null, + beforeTokens: null, + encodedTokens: null, + }); + + // With a before-source, encodable items carry cl100k counts; unencodable + // ones stay null. + const counted = await proxyTransform.toonEncodeToolResults(items, "Normalized"); + assert.ok(Number.isInteger(counted[0].beforeTokens) && counted[0].beforeTokens > 0); + assert.ok(Number.isInteger(counted[0].encodedTokens) && counted[0].encodedTokens > 0); + assert.equal(counted[2].beforeTokens, null); + assert.equal(counted[2].encodedTokens, null); // Empty batch resolves to an empty array. assert.deepEqual(await proxyTransform.toonEncodeToolResults([]), []); diff --git a/platform/backend/src/routes/proxy/__bench__/bench-token-count.ts b/platform/backend/src/routes/proxy/__bench__/bench-token-count.ts new file mode 100644 index 00000000000..19b6c29fc00 --- /dev/null +++ b/platform/backend/src/routes/proxy/__bench__/bench-token-count.ts @@ -0,0 +1,261 @@ +/** + * Profiling bench: what does the JS token counting on the TOON keep/reject + * decision path actually cost, and would moving it into the off-thread native + * kernel be justified? + * + * The current path per request is: 1 native encode (off-thread, libuv) then + * `2 x N` synchronous `getTokenizer(provider).countTokens([...])` calls on the + * event loop (before = normalized, after = encoded), each a WASM cl100k encode + * unless the per-message memo (base.ts) hits. + * + * This measures three regimes that decide the question: + * 1. sequential per-candidate cost, COLD (unique content, cache miss — Rust's + * win case) vs WARM (repeated history, cache hit — Rust's regression risk); + * 2. event-loop delay under 8-way concurrency with counting ON vs OFF (what + * off-loading the synchronous counting to libuv would recover); + * 3. transient JS heap allocation from the token-id arrays. + * + * Run from platform/backend (add --expose-gc for the retained/transient split): + * pnpm exec tsx src/routes/proxy/__bench__/bench-token-count.ts + * node --expose-gc --import tsx src/routes/proxy/__bench__/bench-token-count.ts + */ +import "./bench-env"; +import { monitorEventLoopDelay, performance } from "node:perf_hooks"; +import { setImmediate as yieldEventLoop } from "node:timers/promises"; +import { getTokenizer } from "@/tokenizers"; +import { toonEncodeToolResults } from "../utils/toon-native"; +import { fmt, summarize } from "./bench-util"; +import { + batchBytes, + buildBatch, + CORPUS_SPECS, + type CorpusSpec, +} from "./corpus"; +import { encodeBatchNative, type ToonKernelItem } from "./toon-backend"; + +// All five tiktoken-family TOON adapters count with cl100k under role "user" +// (see base.ts getEncodableText). openai stands in for the whole family. +const PROVIDER = "openai" as const; +const TIME_BUDGET_MS = 3_000; +const MIN_ITERATIONS = 5; +const MAX_ITERATIONS = 400; +const CONCURRENCY = 8; + +let sink = 0; + +// A candidate as the decision loop sees it: the two strings it tokenizes. +interface Countable { + before: string; + after: string; +} + +/** Run the batch through the real native encoder, keep only encodable items. */ +async function encodeCountables(items: ToonKernelItem[]): Promise { + const results = await encodeBatchNative(items); + const countables: Countable[] = []; + results.forEach((r, i) => { + // Skip non-JSON / budget-rejected items — the adapters skip counting them. + if (r.encoded !== null) { + countables.push({ before: items[i].rawContent, after: r.encoded }); + } + }); + return countables; +} + +/** The exact per-candidate work the adapters do: two role-"user" counts. */ +function countOnce(items: Countable[], nonce: string): void { + const tokenizer = getTokenizer(PROVIDER); + for (const { before, after } of items) { + // A non-empty nonce forces a fresh cache key (cold path); "" replays the + // same strings so the second+ pass hits the memo (warm path). + const b = tokenizer.countTokens([ + { role: "user", content: nonce + before }, + ]); + const a = tokenizer.countTokens([{ role: "user", content: nonce + after }]); + sink += b + a; + } +} + +function timedIterations(run: (iter: number) => void): { + meanMs: number; + minMs: number; + iterations: number; +} { + const samples: number[] = []; + const budgetStart = performance.now(); + while ( + samples.length < MAX_ITERATIONS && + (samples.length < MIN_ITERATIONS || + performance.now() - budgetStart < TIME_BUDGET_MS) + ) { + const start = performance.now(); + run(samples.length); + samples.push(performance.now() - start); + } + const s = summarize(samples); + return { meanMs: s.meanMs, minMs: s.minMs, iterations: s.iterations }; +} + +async function section1Sequential(): Promise { + console.info( + "\n[1] Sequential JS token counting cost per candidate (cl100k, 2 counts each)", + ); + console.info( + " warm = repeated content (memo hit); cold = unique content (memo miss, real WASM encode)", + ); + for (const spec of CORPUS_SPECS) { + if (spec.payloadBytes > 1 << 20) { + continue; // 1KB..1MB spans realistic tool-result sizes; skip 5MB here. + } + const items = await encodeCountables(buildBatch(spec, 42)); + if (items.length === 0) { + continue; + } + // Warm: same strings every pass — after the first, all memo hits. + const warm = timedIterations(() => countOnce(items, "")); + // Cold: a unique per-iteration prefix defeats the memo every pass. + const cold = timedIterations((iter) => countOnce(items, `c${iter}_`)); + + const perCandWarmUs = (warm.meanMs / items.length) * 1000; + const perCandColdUs = (cold.meanMs / items.length) * 1000; + console.info( + [ + spec.name.padEnd(6), + `cand=${String(items.length).padStart(4)}`, + `cold=${fmt(perCandColdUs, 1).padStart(8)}us/cand`, + `warm=${fmt(perCandWarmUs, 2).padStart(8)}us/cand`, + `cold/warm=${fmt(perCandColdUs / Math.max(perCandWarmUs, 1e-6), 0).padStart(5)}x`, + `coldBatch=${fmt(cold.minMs).padStart(8)}ms`, + ].join(" "), + ); + } +} + +async function section2EventLoop(): Promise { + console.info("\n[2] Event-loop delay under 8 concurrent batches:"); + console.info( + " none = encode only; js = encode + synchronous WASM count (old); native = encode + fused count (new, off-thread)", + ); + const batches: ToonKernelItem[][] = []; + for (let i = 0; i < CONCURRENCY; i++) { + // Mixed 1KB..100KB, ~realistic tool-result batches. + batches.push( + [ + { name: "100KB", payloadBytes: 100 << 10, count: 8 }, + { name: "10KB", payloadBytes: 10 << 10, count: 24 }, + { name: "1KB", payloadBytes: 1 << 10, count: 48 }, + ].flatMap((p: CorpusSpec, j) => buildBatch(p, 2000 + i * 17 + j * 5)), + ); + } + const totalMB = batches.reduce((s, b) => s + batchBytes(b), 0) / (1 << 20); + + for (const mode of ["none", "js", "native"] as const) { + let peakRss = 0; + const sampleRss = () => { + const rss = process.memoryUsage().rss; + if (rss > peakRss) { + peakRss = rss; + } + }; + const tokenizer = getTokenizer(PROVIDER); + const worker = async (items: ToonKernelItem[]) => { + for (const item of items) { + if (mode === "native") { + // New path: encode + count in one off-thread native call. + const results = await toonEncodeToolResults( + [{ id: "b", rawContent: item.rawContent, unwrap: item.unwrap }], + "normalized", + ); + const r = results?.[0]; + if (r) { + sink += (r.beforeTokens ?? 0) + (r.encodedTokens ?? 0); + } + } else { + const [r] = await encodeBatchNative([item]); + if (mode === "js" && r.encoded !== null) { + // Old path: synchronous WASM counting on the event loop, unique per + // item so the memo never hides the cost (worst case). + sink += tokenizer.countTokens([ + { + role: "user", + content: `${item.rawContent.length}:${item.rawContent}`, + }, + ]); + sink += tokenizer.countTokens([ + { role: "user", content: r.encoded }, + ]); + } + } + sampleRss(); + await yieldEventLoop(); + } + }; + const histogram = monitorEventLoopDelay({ resolution: 10 }); + const rssTimer = setInterval(sampleRss, 25); + histogram.enable(); + const start = performance.now(); + await Promise.all(batches.map((b) => worker(b))); + const wallMs = performance.now() - start; + histogram.disable(); + clearInterval(rssTimer); + const toMs = (ns: number) => ns / 1e6; + console.info( + [ + `mode=${mode.padEnd(6)}`, + `total=${fmt(totalMB, 1)}MB`, + `wall=${fmt(wallMs).padStart(9)}ms`, + `elDelay p50=${fmt(toMs(histogram.percentile(50))).padStart(7)}ms`, + `p99=${fmt(toMs(histogram.percentile(99))).padStart(8)}ms`, + `max=${fmt(toMs(histogram.max)).padStart(9)}ms`, + `peakRss=${fmt(peakRss / (1 << 20), 1)}MB`, + ].join(" "), + ); + } +} + +async function section3Memory(): Promise { + console.info( + "\n[3] Transient JS heap from token-id arrays (cold counting of a 100KB x 64 batch)", + ); + const items = await encodeCountables( + buildBatch({ name: "100KB", payloadBytes: 100 << 10, count: 64 }, 7), + ); + const gc = (globalThis as { gc?: () => void }).gc; + if (gc) { + gc(); + } + const before = process.memoryUsage(); + const PASSES = 50; + for (let p = 0; p < PASSES; p++) { + countOnce(items, `m${p}_`); // cold every pass + } + const afterNoGc = process.memoryUsage(); + if (gc) { + gc(); + } + const afterGc = process.memoryUsage(); + const mb = (n: number) => fmt(n / (1 << 20), 1); + console.info( + ` heapUsed before=${mb(before.heapUsed)}MB afterLoop=${mb(afterNoGc.heapUsed)}MB afterGC=${mb(afterGc.heapUsed)}MB`, + ); + console.info( + ` external before=${mb(before.external)}MB afterLoop=${mb(afterNoGc.external)}MB afterGC=${mb(afterGc.external)}MB`, + ); + console.info( + gc + ? " (afterGC ~= before means the counting churn is fully transient, not retained)" + : " (re-run with --expose-gc to separate transient churn from retained heap)", + ); +} + +async function main(): Promise { + console.info( + "bench-token-count: current JS-side cl100k counting on the TOON decision path", + ); + await section1Sequential(); + await section2EventLoop(); + await section3Memory(); + console.info(`\n(sink=${sink})`); +} + +main(); diff --git a/platform/backend/src/routes/proxy/adapters/cohere.ts b/platform/backend/src/routes/proxy/adapters/cohere.ts index 7eea658002f..091682c968c 100644 --- a/platform/backend/src/routes/proxy/adapters/cohere.ts +++ b/platform/backend/src/routes/proxy/adapters/cohere.ts @@ -5,7 +5,6 @@ import config from "@/config"; import logger from "@/logging"; import { ModelModel } from "@/models"; import { metrics } from "@/observability"; -import { getTokenizer } from "@/tokenizers"; import type { ChunkProcessingResult, Cohere, @@ -736,8 +735,6 @@ export async function convertToolResultsToToon( messages: CohereMessages; stats: CompressionStats; }> { - const tokenizer = getTokenizer("cohere"); - let totalTokensBefore = 0; let totalTokensAfter = 0; @@ -762,6 +759,7 @@ export async function convertToolResultsToToon( rawContent: candidate.message.content, unwrap: true, })), + "normalized", ) : []; @@ -783,10 +781,21 @@ export async function convertToolResultsToToon( const result = [...messages]; candidates.forEach((candidate, candidateIndex) => { - const { normalized, encoded: compressed } = encodedResults[candidateIndex]; + const { + normalized, + encoded: compressed, + beforeTokens, + encodedTokens, + } = encodedResults[candidateIndex]; const { message: toolMsg } = candidate; - if (compressed === null) { + // Counts come from the native pass (on the "normalized" baseline). Present + // iff the item was encoded; a null compressed output means non-JSON content. + if ( + compressed === null || + beforeTokens === null || + encodedTokens === null + ) { logger.info( { toolCallId: toolMsg.tool_call_id, @@ -797,13 +806,8 @@ export async function convertToolResultsToToon( return; } - // Token accounting on the normalized (unwrapped) string, exactly as before. - const tokensBefore = tokenizer.countTokens([ - { role: "user", content: normalized }, - ]); - const tokensAfter = tokenizer.countTokens([ - { role: "user", content: compressed }, - ]); + const tokensBefore = beforeTokens; + const tokensAfter = encodedTokens; // Only use TOON compression if it actually saves tokens — Cohere counts // totals only for wins. diff --git a/platform/backend/src/routes/proxy/adapters/gemini.ts b/platform/backend/src/routes/proxy/adapters/gemini.ts index d47daaf9efb..455c8f43d81 100644 --- a/platform/backend/src/routes/proxy/adapters/gemini.ts +++ b/platform/backend/src/routes/proxy/adapters/gemini.ts @@ -16,7 +16,6 @@ import config from "@/config"; import logger from "@/logging"; import { ModelModel } from "@/models"; import { metrics } from "@/observability"; -import { getTokenizer } from "@/tokenizers"; import type { ChunkProcessingResult, CommonMcpToolDefinition, @@ -866,7 +865,6 @@ async function convertToolResultsToToon( contents: GeminiContents; stats: ToolCompressionStats; }> { - const tokenizer = getTokenizer("gemini"); let toolResultCount = 0; let totalTokensBefore = 0; let totalTokensAfter = 0; @@ -958,6 +956,7 @@ async function convertToolResultsToToon( rawContent: candidate.rawContent, unwrap: true, })), + "raw", ) : []; @@ -994,12 +993,23 @@ async function convertToolResultsToToon( }; candidates.forEach((candidate, candidateIndex) => { - const { encoded: compressed } = encodedResults[candidateIndex]; + const { + encoded: compressed, + beforeTokens, + encodedTokens, + } = encodedResults[candidateIndex]; const { functionResponse, rawContent: noncompressed } = candidate; const functionName = "name" in functionResponse ? functionResponse.name : "unknown"; - if (compressed === null) { + // Counts come from the native pass on the "raw" baseline — the original + // serialization, matching Gemini's accounting. Present iff the item was + // encoded; a null compressed output means it could not be compressed. + if ( + compressed === null || + beforeTokens === null || + encodedTokens === null + ) { logger.info( { functionName }, "convertToolResultsToToon: skipping - response cannot be compressed", @@ -1007,14 +1017,8 @@ async function convertToolResultsToToon( return; } - // Token accounting on the ORIGINAL serialization (not the unwrapped - // string), exactly as before. - const tokensBefore = tokenizer.countTokens([ - { role: "user", content: noncompressed }, - ]); - const tokensAfter = tokenizer.countTokens([ - { role: "user", content: compressed }, - ]); + const tokensBefore = beforeTokens; + const tokensAfter = encodedTokens; // Always count tokens totalTokensBefore += tokensBefore; diff --git a/platform/backend/src/routes/proxy/adapters/minimax.ts b/platform/backend/src/routes/proxy/adapters/minimax.ts index 5daa6a23435..1c6ed55b4a9 100644 --- a/platform/backend/src/routes/proxy/adapters/minimax.ts +++ b/platform/backend/src/routes/proxy/adapters/minimax.ts @@ -899,7 +899,6 @@ async function convertToolResultsToToon( messages: MinimaxMessages, model: string, ): Promise<{ messages: MinimaxMessages; stats: ToolCompressionStats }> { - const tokenizer = getTokenizer("minimax"); let toolResultCount = 0; let totalTokensBefore = 0; let totalTokensAfter = 0; @@ -938,6 +937,7 @@ async function convertToolResultsToToon( rawContent: candidate.content, unwrap: true, })), + "normalized", ) : []; @@ -959,10 +959,21 @@ async function convertToolResultsToToon( const result = [...messages]; candidates.forEach((candidate, candidateIndex) => { - const { normalized, encoded: compressed } = encodedResults[candidateIndex]; + const { + encoded: compressed, + beforeTokens, + encodedTokens, + } = encodedResults[candidateIndex]; const { message } = candidate; - if (compressed === null) { + // Counts come from the native pass (on the "normalized" baseline). Present + // iff the item was encoded; a null compressed output means it could not be + // compressed. + if ( + compressed === null || + beforeTokens === null || + encodedTokens === null + ) { logger.warn( { toolCallId: message.tool_call_id }, "Failed to compress tool result", @@ -970,13 +981,8 @@ async function convertToolResultsToToon( return; } - // Token accounting on the normalized (unwrapped) string, exactly as before. - const tokensBefore = tokenizer.countTokens([ - { role: "user", content: normalized }, - ]); - const tokensAfter = tokenizer.countTokens([ - { role: "user", content: compressed }, - ]); + const tokensBefore = beforeTokens; + const tokensAfter = encodedTokens; totalTokensBefore += tokensBefore; totalTokensAfter += tokensAfter; diff --git a/platform/backend/src/routes/proxy/adapters/openai.ts b/platform/backend/src/routes/proxy/adapters/openai.ts index 8ee01d98dbd..2448a9929fb 100644 --- a/platform/backend/src/routes/proxy/adapters/openai.ts +++ b/platform/backend/src/routes/proxy/adapters/openai.ts @@ -13,7 +13,6 @@ import config from "@/config"; import logger from "@/logging"; import { ModelModel } from "@/models"; import { metrics } from "@/observability"; -import { getTokenizer } from "@/tokenizers"; import type { ChunkProcessingResult, CommonMcpToolDefinition, @@ -1270,7 +1269,6 @@ export async function convertToolResultsToToon( messages: OpenAiMessages; stats: ToolCompressionStats; }> { - const tokenizer = getTokenizer(provider); let toolResultCount = 0; let totalTokensBefore = 0; let totalTokensAfter = 0; @@ -1309,6 +1307,7 @@ export async function convertToolResultsToToon( rawContent: candidate.content, unwrap: true, })), + "normalized", ) : []; @@ -1330,10 +1329,22 @@ export async function convertToolResultsToToon( const result = [...messages]; candidates.forEach((candidate, candidateIndex) => { - const { normalized, encoded: compressed } = encodedResults[candidateIndex]; + const { + normalized, + encoded: compressed, + beforeTokens, + encodedTokens, + } = encodedResults[candidateIndex]; const { message } = candidate; - if (compressed === null) { + // Counts come from the native pass (on the "normalized" baseline). They are + // present iff the item was encoded; a null compressed output means the + // content was not JSON — keep the original either way. + if ( + compressed === null || + beforeTokens === null || + encodedTokens === null + ) { logger.info( { toolCallId: message.tool_call_id, @@ -1344,13 +1355,8 @@ export async function convertToolResultsToToon( return; } - // Token accounting on the normalized (unwrapped) string, exactly as before. - const tokensBefore = tokenizer.countTokens([ - { role: "user", content: normalized }, - ]); - const tokensAfter = tokenizer.countTokens([ - { role: "user", content: compressed }, - ]); + const tokensBefore = beforeTokens; + const tokensAfter = encodedTokens; toolResultCount++; diff --git a/platform/backend/src/routes/proxy/adapters/toon-compression-addon-unavailable.test.ts b/platform/backend/src/routes/proxy/adapters/toon-compression-addon-unavailable.test.ts index 1b1c2d761c4..a3642004685 100644 --- a/platform/backend/src/routes/proxy/adapters/toon-compression-addon-unavailable.test.ts +++ b/platform/backend/src/routes/proxy/adapters/toon-compression-addon-unavailable.test.ts @@ -41,9 +41,21 @@ const EXPECTED_STATS: ToolCompressionStats = { function expectSingleBatchCall( expectedItems: { id: string; rawContent: string; unwrap: boolean }[], + beforeSource?: "raw" | "normalized", ) { expect(vi.mocked(toonEncodeToolResults)).toHaveBeenCalledTimes(1); - expect(vi.mocked(toonEncodeToolResults)).toHaveBeenCalledWith(expectedItems); + // Anthropic and Bedrock keep their own tokenizer and call with one argument; + // the tiktoken-family adapters request native counting with a before-source. + if (beforeSource === undefined) { + expect(vi.mocked(toonEncodeToolResults)).toHaveBeenCalledWith( + expectedItems, + ); + } else { + expect(vi.mocked(toonEncodeToolResults)).toHaveBeenCalledWith( + expectedItems, + beforeSource, + ); + } } describe("adapters with the TOON addon unavailable", () => { @@ -99,10 +111,13 @@ describe("adapters with the TOON addon unavailable", () => { const adapter = geminiAdapterFactory.createRequestAdapter({ contents }); const stats = await adapter.applyToonCompression("gemini-2.0-flash"); - expectSingleBatchCall([ - { id: "list_files", rawContent: TOOL_RESULT_JSON_A, unwrap: true }, - { id: "read_config", rawContent: TOOL_RESULT_JSON_B, unwrap: true }, - ]); + expectSingleBatchCall( + [ + { id: "list_files", rawContent: TOOL_RESULT_JSON_A, unwrap: true }, + { id: "read_config", rawContent: TOOL_RESULT_JSON_B, unwrap: true }, + ], + "raw", + ); expect(stats).toStrictEqual(EXPECTED_STATS); expect(adapter.getProviderMessages()).toBe(contents); }); @@ -162,10 +177,13 @@ describe("adapters with the TOON addon unavailable", () => { }); const stats = await adapter.applyToonCompression("glm-4.6"); - expectSingleBatchCall([ - { id: "call_1", rawContent: TOOL_RESULT_JSON_A, unwrap: true }, - { id: "call_2", rawContent: TOOL_RESULT_JSON_B, unwrap: true }, - ]); + expectSingleBatchCall( + [ + { id: "call_1", rawContent: TOOL_RESULT_JSON_A, unwrap: true }, + { id: "call_2", rawContent: TOOL_RESULT_JSON_B, unwrap: true }, + ], + "normalized", + ); expect(stats).toStrictEqual(EXPECTED_STATS); expect(adapter.getProviderMessages()).toBe(messages); }); @@ -189,10 +207,13 @@ describe("adapters with the TOON addon unavailable", () => { }); const stats = await adapter.applyToonCompression("MiniMax-M2"); - expectSingleBatchCall([ - { id: "call_1", rawContent: TOOL_RESULT_JSON_A, unwrap: true }, - { id: "call_2", rawContent: TOOL_RESULT_JSON_B, unwrap: true }, - ]); + expectSingleBatchCall( + [ + { id: "call_1", rawContent: TOOL_RESULT_JSON_A, unwrap: true }, + { id: "call_2", rawContent: TOOL_RESULT_JSON_B, unwrap: true }, + ], + "normalized", + ); expect(stats).toStrictEqual(EXPECTED_STATS); expect(adapter.getProviderMessages()).toBe(messages); }); @@ -216,10 +237,13 @@ describe("adapters with the TOON addon unavailable", () => { }); const stats = await adapter.applyToonCompression("command-r-plus"); - expectSingleBatchCall([ - { id: "call_1", rawContent: TOOL_RESULT_JSON_A, unwrap: true }, - { id: "call_2", rawContent: TOOL_RESULT_JSON_B, unwrap: true }, - ]); + expectSingleBatchCall( + [ + { id: "call_1", rawContent: TOOL_RESULT_JSON_A, unwrap: true }, + { id: "call_2", rawContent: TOOL_RESULT_JSON_B, unwrap: true }, + ], + "normalized", + ); expect(stats).toStrictEqual(EXPECTED_STATS); expect(adapter.getProviderMessages()).toBe(messages); }); diff --git a/platform/backend/src/routes/proxy/adapters/zhipuai.ts b/platform/backend/src/routes/proxy/adapters/zhipuai.ts index 93363328b3e..afece76680f 100644 --- a/platform/backend/src/routes/proxy/adapters/zhipuai.ts +++ b/platform/backend/src/routes/proxy/adapters/zhipuai.ts @@ -7,7 +7,6 @@ import config from "@/config"; import logger from "@/logging"; import { ModelModel } from "@/models"; import { metrics } from "@/observability"; -import { getTokenizer } from "@/tokenizers"; import type { ChunkProcessingResult, CommonMcpToolDefinition, @@ -825,7 +824,6 @@ async function convertToolResultsToToon( messages: ZhipuaiMessages; stats: ToolCompressionStats; }> { - const tokenizer = getTokenizer("zhipuai"); let toolResultCount = 0; let totalTokensBefore = 0; let totalTokensAfter = 0; @@ -864,6 +862,7 @@ async function convertToolResultsToToon( rawContent: candidate.content, unwrap: true, })), + "normalized", ) : []; @@ -885,10 +884,21 @@ async function convertToolResultsToToon( const result = [...messages]; candidates.forEach((candidate, candidateIndex) => { - const { normalized, encoded: compressed } = encodedResults[candidateIndex]; + const { + normalized, + encoded: compressed, + beforeTokens, + encodedTokens, + } = encodedResults[candidateIndex]; const { message } = candidate; - if (compressed === null) { + // Counts come from the native pass (on the "normalized" baseline). Present + // iff the item was encoded; a null compressed output means non-JSON content. + if ( + compressed === null || + beforeTokens === null || + encodedTokens === null + ) { logger.info( { toolCallId: message.tool_call_id, @@ -899,13 +909,8 @@ async function convertToolResultsToToon( return; } - // Token accounting on the normalized (unwrapped) string, exactly as before. - const tokensBefore = tokenizer.countTokens([ - { role: "user", content: normalized }, - ]); - const tokensAfter = tokenizer.countTokens([ - { role: "user", content: compressed }, - ]); + const tokensBefore = beforeTokens; + const tokensAfter = encodedTokens; toolResultCount++; diff --git a/platform/backend/src/routes/proxy/utils/toon-native-token-parity.test.ts b/platform/backend/src/routes/proxy/utils/toon-native-token-parity.test.ts new file mode 100644 index 00000000000..a1f10e0deed --- /dev/null +++ b/platform/backend/src/routes/proxy/utils/toon-native-token-parity.test.ts @@ -0,0 +1,79 @@ +// Differential guard on the cl100k parity contract: the native fused counts +// (tiktoken-rs) must equal the JS `tiktoken` tokenizer over the exact strings +// each adapter compares — the golden corpus plus adversarial unicode / +// whitespace / reserved-marker inputs. A tiktoken-rs bump that shifts a single +// token here changes recorded compression stats, so this pins native == JS +// directly rather than only through the per-adapter number pins. + +import { expect, test } from "@/test"; +import { allGoldenEntries, describeNative } from "@/test/toon-golden"; +import { getTokenizer } from "@/tokenizers"; +import { toonEncodeToolResults } from "./toon-native"; + +const countJs = (content: string) => + getTokenizer("openai").countTokens([{ role: "user", content }]); + +const ADVERSARIAL = [ + "", + " \n\t\r ", + "café résumé naïve Ångström", + "日本語 中文 한국어 emoji 🚀🔥✨🎉", + "<|endoftext|> reserved <|fim_prefix|> markers <|fim_suffix|>", + "a".repeat(5000), + JSON.stringify({ + rows: Array.from({ length: 60 }, (_, i) => ({ id: i, name: `row ${i}` })), + }), +]; + +describeNative("native cl100k counting matches the JS tokenizer", () => { + test("golden corpus + adversarial inputs count identically to JS", async () => { + const inputs = [ + ...allGoldenEntries().map((entry) => ({ + rawContent: entry.rawContent, + unwrap: entry.unwrap, + })), + ...ADVERSARIAL.map((rawContent) => ({ rawContent, unwrap: false })), + ]; + + const results = await toonEncodeToolResults( + inputs.map((input, i) => ({ id: `p${i}`, ...input })), + "normalized", + ); + expect(results).not.toBeNull(); + if (results === null) return; + expect(results).toHaveLength(inputs.length); + + for (const result of results) { + if (result.encoded === null) { + // Unencodable items are never tokenized. + expect(result.beforeTokens).toBeNull(); + expect(result.encodedTokens).toBeNull(); + continue; + } + // Counts are taken on the exact post-transform strings the adapter uses, + // so any boundary normalization applies equally to both sides. + expect(result.beforeTokens).toBe(countJs(result.normalized)); + expect(result.encodedTokens).toBe(countJs(result.encoded)); + } + }); + + test("the Raw baseline counts the original pre-unwrap serialization", async () => { + // A wrapped payload: Raw counts the whole wrapper (what Gemini tokenizes), + // not the unwrapped inner text that Normalized would. + const inner = JSON.stringify({ data: [{ id: 1 }, { id: 2 }, { id: 3 }] }); + const wrapped = JSON.stringify([{ type: "text", text: inner }]); + + const results = await toonEncodeToolResults( + [{ id: "w", rawContent: wrapped, unwrap: true }], + "raw", + ); + expect(results).not.toBeNull(); + if (results === null) return; + + const [result] = results; + expect(result.encoded).not.toBeNull(); + expect(result.beforeTokens).toBe(countJs(wrapped)); + // Sanity: the raw wrapper is larger than the unwrapped inner text. + expect(countJs(wrapped)).toBeGreaterThan(countJs(inner)); + }); +}); diff --git a/platform/backend/src/routes/proxy/utils/toon-native.test.ts b/platform/backend/src/routes/proxy/utils/toon-native.test.ts index e890aede527..91e755360e5 100644 --- a/platform/backend/src/routes/proxy/utils/toon-native.test.ts +++ b/platform/backend/src/routes/proxy/utils/toon-native.test.ts @@ -24,21 +24,113 @@ describe("toonEncodeToolResults helper", () => { test("returns the positional native results on success", async () => { const nativeResults = [ - { normalized: '{"a":[1,2]}', encoded: "a[2]: 1,2" }, - { normalized: "not json", encoded: null }, + { + normalized: '{"a":[1,2]}', + encoded: "a[2]: 1,2", + beforeTokens: null, + encodedTokens: null, + }, + { + normalized: "not json", + encoded: null, + beforeTokens: null, + encodedTokens: null, + }, ]; vi.mocked(nativeToonEncodeToolResults).mockResolvedValue(nativeResults); const results = await toonEncodeToolResults(items); expect(results).toStrictEqual(nativeResults); - expect(nativeToonEncodeToolResults).toHaveBeenCalledWith(items); + expect(nativeToonEncodeToolResults).toHaveBeenCalledWith(items, undefined); expect(metrics.llm.reportToonAddonUnavailable).not.toHaveBeenCalled(); }); + test("requests native counting and returns the counts", async () => { + const nativeResults = [ + { + normalized: '{"a":[1,2]}', + encoded: "a[2]: 1,2", + beforeTokens: 8, + encodedTokens: 5, + }, + { + normalized: "not json", + encoded: null, + beforeTokens: null, + encodedTokens: null, + }, + ]; + vi.mocked(nativeToonEncodeToolResults).mockResolvedValue(nativeResults); + + const results = await toonEncodeToolResults(items, "normalized"); + + expect(results).toStrictEqual(nativeResults); + // The friendly union maps to the native string_enum value. + expect(nativeToonEncodeToolResults).toHaveBeenCalledWith( + items, + "Normalized", + ); + }); + + test("maps the gemini raw baseline to the native Raw source", async () => { + vi.mocked(nativeToonEncodeToolResults).mockResolvedValue([ + { + normalized: "not json", + encoded: null, + beforeTokens: null, + encodedTokens: null, + }, + ]); + + await toonEncodeToolResults([items[1]], "raw"); + + expect(nativeToonEncodeToolResults).toHaveBeenCalledWith([items[1]], "Raw"); + }); + + test("returns null when a requested count is missing for an encoded item", async () => { + // Native tokenizer unavailable: encoding succeeded but counts came back + // null. The keep/reject decision cannot proceed, so fail open. + vi.mocked(nativeToonEncodeToolResults).mockResolvedValue([ + { + normalized: '{"a":[1,2]}', + encoded: "a[2]: 1,2", + beforeTokens: null, + encodedTokens: null, + }, + ]); + + const results = await toonEncodeToolResults([items[0]], "normalized"); + + expect(results).toBeNull(); + expect(metrics.llm.reportToonAddonUnavailable).toHaveBeenCalledWith( + "request", + ); + }); + + test("returns null when counts appear though none were requested", async () => { + vi.mocked(nativeToonEncodeToolResults).mockResolvedValue([ + { + normalized: '{"a":[1,2]}', + encoded: "a[2]: 1,2", + beforeTokens: 8, + encodedTokens: 5, + }, + ]); + + const results = await toonEncodeToolResults([items[0]]); + + expect(results).toBeNull(); + }); + test("returns null when the native batch length does not match the input", async () => { vi.mocked(nativeToonEncodeToolResults).mockResolvedValue([ - { normalized: '{"a":[1,2]}', encoded: "a[2]: 1,2" }, + { + normalized: '{"a":[1,2]}', + encoded: "a[2]: 1,2", + beforeTokens: null, + encodedTokens: null, + }, ]); const results = await toonEncodeToolResults(items); diff --git a/platform/backend/src/routes/proxy/utils/toon-native.ts b/platform/backend/src/routes/proxy/utils/toon-native.ts index 14f47d0620d..942641f59a3 100644 --- a/platform/backend/src/routes/proxy/utils/toon-native.ts +++ b/platform/backend/src/routes/proxy/utils/toon-native.ts @@ -5,6 +5,7 @@ // reason instead of fabricating stats. import type { + BeforeSource, ToonEncodeItem, ToonEncodeResult, } from "@archestra/proxy-transform-rs"; @@ -13,25 +14,55 @@ import { metrics } from "@/observability"; export type { ToonEncodeItem, ToonEncodeResult }; +/** + * Which string the adapter tokenizes as the pre-compression baseline, so the + * native pass returns matching `beforeTokens`/`encodedTokens`: `"normalized"` + * (post-unwrap) for most adapters, `"raw"` (the original serialization) for + * Gemini. Omit it to skip counting (Anthropic and Bedrock count with their own + * tokenizer). Adapters pass the string literal, so this stays module-local. + */ +type ToonBeforeSource = "raw" | "normalized"; + +// The native binding types `beforeSource` as a `const enum` (string values +// "Raw"/"Normalized"). esbuild/tsx cannot inline a const enum across modules, +// so adapters must not reference its members; instead they pass this friendly +// union and the underlying string values are supplied here, at the one call +// site, matched to the enum's own values. +const NATIVE_BEFORE_SOURCE: Record = { + raw: "Raw" as unknown as BeforeSource, + normalized: "Normalized" as unknown as BeforeSource, +}; + /** * Transform a batch of tool results (optional client-wrapper unwrap → JSON * parse → TOON encode). Results are positional — same order and length as * `items`; `encoded` is null for content that is not parseable JSON. * + * When `beforeSource` is given, the native pass also returns the cl100k token + * counts (`beforeTokens`/`encodedTokens`) that gate the keep/reject decision, + * off the event loop. They are populated for every encodable item; a requested + * count coming back absent means the native tokenizer is unavailable, treated + * the same as a missing addon (return null, skip compression). + * * Returns null when the native addon is unavailable or misbehaves (callers * must then skip compression entirely and surface `addon_unavailable`). */ export async function toonEncodeToolResults( items: ToonEncodeItem[], + beforeSource?: ToonBeforeSource, ): Promise { try { const native = await loadProxyTransformNative(); - const results = await native.toonEncodeToolResults(items); + const results = await native.toonEncodeToolResults( + items, + beforeSource ? NATIVE_BEFORE_SOURCE[beforeSource] : undefined, + ); if (results.length !== items.length) { throw new Error( `native toonEncodeToolResults returned ${results.length} results for ${items.length} items`, ); } + assertTokenCountInvariant(results, beforeSource !== undefined); return results; } catch (error) { logger.error( @@ -61,6 +92,33 @@ export async function initToonNative(): Promise { } } +// Guard the token-count contract at the boundary: a requested count that comes +// back absent, or a stray count when none was requested, would silently corrupt +// the keep/reject decision (NaN comparisons, bogus stats). Throwing routes it +// into the fail-open path (return null → addon_unavailable) instead. +function assertTokenCountInvariant( + results: ToonEncodeResult[], + counted: boolean, +): void { + for (const { encoded, beforeTokens, encodedTokens } of results) { + if (counted && encoded !== null) { + if (!isCount(beforeTokens) || !isCount(encodedTokens)) { + throw new Error( + "native toonEncodeToolResults omitted token counts for an encoded item", + ); + } + } else if (beforeTokens !== null || encodedTokens !== null) { + throw new Error( + "native toonEncodeToolResults returned token counts that were not requested", + ); + } + } +} + +function isCount(value: number | null): value is number { + return value !== null && Number.isInteger(value) && value >= 0; +} + // Lazy, memoized load of the native addon: codegen and paths that never // compress tool results don't require the built `.node`. Mirrors // utils/image-conversion.ts and the sandbox/app-runtime native loaders. diff --git a/platform/backend/src/test/toon-golden.ts b/platform/backend/src/test/toon-golden.ts index bf8188af0d3..71252d68ac5 100644 --- a/platform/backend/src/test/toon-golden.ts +++ b/platform/backend/src/test/toon-golden.ts @@ -23,6 +23,11 @@ export function corpusEntry(name: string): GoldenEntry { return entry; } +/** Every golden entry — for suites that sweep the whole corpus. */ +export function allGoldenEntries(): GoldenEntry[] { + return corpus; +} + export function makeCountTokens(provider: SupportedProvider) { const tokenizer = getTokenizer(provider); return (content: string) => diff --git a/platform/backend/src/tokenizers/tiktoken.ts b/platform/backend/src/tokenizers/tiktoken.ts index 3b19a07aa7d..a62ff82b685 100644 --- a/platform/backend/src/tokenizers/tiktoken.ts +++ b/platform/backend/src/tokenizers/tiktoken.ts @@ -16,6 +16,11 @@ export class TiktokenTokenizer extends BaseTokenizer { } protected computeMessageTokens(encodableText: string): number { - return this.encoding.encode(encodableText).length; + // Ordinary encoding: treat reserved-marker literals (e.g. `<|endoftext|>`) + // that appear in tool results as plain text. `encode()` raises on them by + // default, which would crash counting on otherwise-valid content; the + // native cl100k path (proxy-transform-core) counts ordinally too, so this + // keeps the two byte-identical. + return this.encoding.encode_ordinary(encodableText).length; } } diff --git a/platform/backend/src/tokenizers/tokenizers.test.ts b/platform/backend/src/tokenizers/tokenizers.test.ts index 9c14ab872cf..f91bd75bed2 100644 --- a/platform/backend/src/tokenizers/tokenizers.test.ts +++ b/platform/backend/src/tokenizers/tokenizers.test.ts @@ -60,6 +60,21 @@ describe("Tokenizers", () => { // Should at least count the role expect(tokenCount).toBeGreaterThanOrEqual(0); }); + + test("counts reserved-marker literals as text instead of throwing", () => { + // A tool result can contain `<|endoftext|>` verbatim; the default + // `encode()` raises on reserved markers, which would crash counting. + // Ordinary encoding counts them as plain text. + const tokenizer = new TiktokenTokenizer(); + const message: ProviderMessage = { + role: "user", + content: "row 1 <|endoftext|> row 2 <|fim_prefix|>", + }; + + const tokenCount = tokenizer.countTokens(message); + + expect(tokenCount).toBeGreaterThan(0); + }); }); describe("AnthropicTokenizer", () => { From 5c490b7b10daec6caecfa5ff0685ff49e8e2df03 Mon Sep 17 00:00:00 2001 From: Arseny Kravchenko Date: Sat, 11 Jul 2026 00:00:48 +0200 Subject: [PATCH 17/18] test(proxy): compare adversarial inputs in the native token parity test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review gate: the adversarial strings (unicode, whitespace, reserved-token literals) were passed as bare non-JSON, so they hit encoded=null and only asserted null counts — never comparing native to JS. Wrap them in encodable JSON so the count comparison actually runs on them, and assert it covered at least the adversarial set. The reserved-marker case now doubles as the encode_ordinary regression (counting it would throw under the old encode()). Also disclose the per-item batching in the concurrency bench (mirrors bench-concurrency.ts): wall/RSS are directional, event-loop delay faithful. Claude-Session: https://claude.ai/code/session_01QkeLJprfeq75d2NY39aFiu --- .../proxy/__bench__/bench-token-count.ts | 6 ++++ .../utils/toon-native-token-parity.test.ts | 28 ++++++++++++------- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/platform/backend/src/routes/proxy/__bench__/bench-token-count.ts b/platform/backend/src/routes/proxy/__bench__/bench-token-count.ts index 19b6c29fc00..c96c37edab2 100644 --- a/platform/backend/src/routes/proxy/__bench__/bench-token-count.ts +++ b/platform/backend/src/routes/proxy/__bench__/bench-token-count.ts @@ -149,6 +149,12 @@ async function section2EventLoop(): Promise { } const totalMB = batches.reduce((s, b) => s + batchBytes(b), 0) / (1 << 20); + // Each worker processes items one at a time with an event-loop yield between + // them, mirroring bench-concurrency.ts. This isolates event-loop delay (the + // metric that matters): a production request batches its results into one + // native call, so the absolute wall/RSS figures here are directional, but the + // p50/p99/max event-loop delay per mode is faithful — synchronous JS counting + // stalls the loop, the off-thread native call does not. for (const mode of ["none", "js", "native"] as const) { let peakRss = 0; const sampleRss = () => { diff --git a/platform/backend/src/routes/proxy/utils/toon-native-token-parity.test.ts b/platform/backend/src/routes/proxy/utils/toon-native-token-parity.test.ts index a1f10e0deed..f5ca49986bf 100644 --- a/platform/backend/src/routes/proxy/utils/toon-native-token-parity.test.ts +++ b/platform/backend/src/routes/proxy/utils/toon-native-token-parity.test.ts @@ -13,17 +13,20 @@ import { toonEncodeToolResults } from "./toon-native"; const countJs = (content: string) => getTokenizer("openai").countTokens([{ role: "user", content }]); +// Each adversarial payload is wrapped in encodable JSON so the strings actually +// reach the count comparison (a bare non-JSON string yields encoded=null and is +// never tokenized). The reserved-marker case doubles as the regression for the +// encode_ordinary baseline: counting it on both sides would throw under the old +// `encode()`. const ADVERSARIAL = [ - "", - " \n\t\r ", - "café résumé naïve Ångström", - "日本語 中文 한국어 emoji 🚀🔥✨🎉", - "<|endoftext|> reserved <|fim_prefix|> markers <|fim_suffix|>", - "a".repeat(5000), - JSON.stringify({ - rows: Array.from({ length: 60 }, (_, i) => ({ id: i, name: `row ${i}` })), - }), -]; + { v: "" }, + { v: " \n\t\r " }, + { v: "café résumé naïve Ångström" }, + { v: "日本語 中文 한국어 emoji 🚀🔥✨🎉" }, + { v: "<|endoftext|> reserved <|fim_prefix|> markers <|fim_suffix|>" }, + { v: "a".repeat(5000) }, + { rows: Array.from({ length: 60 }, (_, i) => ({ id: i, name: `row ${i}` })) }, +].map((payload) => JSON.stringify(payload)); describeNative("native cl100k counting matches the JS tokenizer", () => { test("golden corpus + adversarial inputs count identically to JS", async () => { @@ -43,6 +46,7 @@ describeNative("native cl100k counting matches the JS tokenizer", () => { if (results === null) return; expect(results).toHaveLength(inputs.length); + let compared = 0; for (const result of results) { if (result.encoded === null) { // Unencodable items are never tokenized. @@ -54,7 +58,11 @@ describeNative("native cl100k counting matches the JS tokenizer", () => { // so any boundary normalization applies equally to both sides. expect(result.beforeTokens).toBe(countJs(result.normalized)); expect(result.encodedTokens).toBe(countJs(result.encoded)); + compared++; } + // The adversarial payloads are all encodable, so the count comparison must + // actually run on them (not just assert null on non-JSON input). + expect(compared).toBeGreaterThanOrEqual(ADVERSARIAL.length); }); test("the Raw baseline counts the original pre-unwrap serialization", async () => { From 287bf342b8610a2dc12a3c131d9ce52f992f7065 Mon Sep 17 00:00:00 2001 From: Arseny Kravchenko Date: Sat, 11 Jul 2026 00:01:55 +0200 Subject: [PATCH 18/18] docs: token counting can come from the native TOON call Step 4 said to always count with the provider tokenizer; cl100k providers now pass a before-source and read counts back from toonEncodeToolResults(). Claude-Session: https://claude.ai/code/session_01QkeLJprfeq75d2NY39aFiu --- docs/pages/platform-adding-llm-providers.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/pages/platform-adding-llm-providers.md b/docs/pages/platform-adding-llm-providers.md index caf0448907a..ab9b5b0f0eb 100644 --- a/docs/pages/platform-adding-llm-providers.md +++ b/docs/pages/platform-adding-llm-providers.md @@ -3,7 +3,7 @@ title: Adding LLM Providers category: Development order: 2 description: Developer guide for implementing new LLM provider support in Archestra Platform -lastUpdated: 2026-07-10 +lastUpdated: 2026-07-11 --- @@ -172,7 +172,7 @@ The function must: 1. Iterate through provider-specific message array structure 2. Find tool result messages (e.g., `role: "tool"` in OpenAI, `tool_result` blocks in Anthropic, `functionResponse` parts in Gemini) 3. Batch the extracted results through `toonEncodeToolResults()` from `backend/src/routes/proxy/utils/toon-native.ts`. The native addon unwraps, parses, and encodes them — never encode TOON in the adapter -4. Count tokens with the provider tokenizer; keep the TOON version only when it saves tokens +4. Get token counts to compare. For cl100k providers, pass a before-source to `toonEncodeToolResults()` and read the counts it returns; Anthropic and Bedrock count with their own tokenizer. Keep the TOON version only when it saves tokens 5. Return compressed messages and compression statistics `toonEncodeToolResults()` returns `null` when the native addon is unavailable. The adapter must then keep the original results and report the `addon_unavailable` skip reason.