Files
learnharness/tests/headless-png.test.ts
amosblomqvist 0a4a9a11de BYOK provider embeddings (wave 6 chunk B): kill local transformers
Replace the @huggingface/transformers local embedder (~316MB ONNX cache)
with an EmbeddingPort seam: graph.embeddingModel (nullable "provider:id"
slug) selects an OpenRouter/OpenAI/Gemini adapter, resolved fresh per
call from settings + the auth store so key rotations land immediately.

- src/server/embeddings.ts rewritten: slug grammar, injected-fetch
  adapters (OpenRouter input_type, Gemini taskType batching, OpenAI
  index-ordered rows), honest resolution (null = skip-with-info, set
  model with missing key = honest error), embedNewNodes with model-change
  re-embed wipe + ghost pruning + usage steps, federated catalog
  (OpenRouter /api/v1/embeddings/models — VERIFIED live, the inverse
  path 404s; OpenAI text-embedding- filter; Gemini embedContent filter).
- GET /api/models/embeddings: federated catalog, empty when no keys.
- Extraction tail records stages.embedding usage into the run's money
  channel; EMBEDDING_PRICES overlay in core/prices.ts (outside the
  sync-generated block), callCost falls back to it; cost-summary folds
  the fourth stage; graph-context injection resolves its embedder per
  run, fails honestly when unconfigured, records query tokens.
- Settings: GraphSettings.embeddingModel (null factory), section parser
  validates slug shape (id may contain colons — OpenRouter :free
  suffixes, live catalog fact).
- Removal: @huggingface/transformers uninstalled, PATHS.modelCache and
  onnx tracing entries deleted, @huggingface tracing-excludes guard
  added, stale sharp/libvips comments updated to historical.

Live-verified on port 3004: catalog federates 36 rows (33 OpenRouter +
3 OpenAI), embedNewNodes embedded 10 nodes via the free liquid/lfm-2.5
model (dim 1024, 435 prompt tokens, 1 call, $0), settings PUT rejects
bad slugs with 400.

Gates: typecheck, lint, 895/895 tests, build.
2026-09-23 03:53:57 +03:00

162 lines
6.4 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import assert from "node:assert/strict";
import { test } from "node:test";
import * as zlib from "node:zlib";
import { providerSafePng } from "../src/agent/subagents/headless.ts";
/**
* The 2026-09-19 visual-maker fix (the "image format is illegal" 400s):
* Chrome writes PNGs with a SPLIT IDAT stream (4KB chunks); Alibaba's vision
* decoder — behind OpenRouter's qwen3.8-flash, the visual-maker's look-model —
* rejected those during the 2026-09-18/19 window while every single-IDAT form
* passed. providerSafePng merges the split back into the canonical ONE-IDAT
* form (PNG spec: the IDAT chunks are an arbitrary split of ONE zlib stream —
* the merged form carries the byte-identical stream). Pure Node on purpose:
* historically the process already carried a native image lib via
* @huggingface/transformers' nested sharp/libvips, and a second copy (root
* sharp) triggered macOS's duplicate-libvips ObjC warning — the transformers
* embedder died with wave 6 (2026-09-23) but the no-native-deps rule lives
* on: app code carries no image library. These tests pin the property with
* no native deps at all.
*/
// ── a tiny PNG toolchain (spec-level, no image library) ─────────────────────
const PNG_SIG = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
function crc32(buf: Buffer): number {
let c = ~0;
for (const byte of buf) {
c ^= byte;
for (let k = 0; k < 8; k++) c = (c >>> 1) ^ (0xedb88320 & -(c & 1));
}
return ~c >>> 0;
}
function chunk(type: string, data: Buffer): Buffer {
const out = Buffer.alloc(12 + data.length);
out.writeUInt32BE(data.length, 0);
out.write(type, 4, "ascii");
data.copy(out, 8);
out.writeUInt32BE(crc32(out.subarray(4, 8 + data.length)), 8 + data.length);
return out;
}
function walkChunks(png: Buffer): Array<{ type: string; data: Buffer }> {
assert.ok(png.subarray(0, 8).equals(PNG_SIG), "PNG signature");
const chunks: Array<{ type: string; data: Buffer }> = [];
let off = 8;
while (off < png.length) {
const len = png.readUInt32BE(off);
const type = png.subarray(off + 4, off + 8).toString("ascii");
chunks.push({ type, data: png.subarray(off + 8, off + 8 + len) });
off += 12 + len;
}
return chunks;
}
/** Build a VALID multi-IDAT PNG — exactly Chrome's shape: one IHDR, the
* compressed scanlines split across ≥2 IDAT chunks, one IEND. */
function chromeStyleMultiIdatPng(width = 4, height = 4): Buffer {
// Raw scanlines: filter byte 0 + width × RGB pixels (white) — the filter
// byte MUST be 0–4 (libpng rejects anything else).
const stride = 1 + width * 3;
const raw = Buffer.alloc(stride * height);
for (let y = 0; y < height; y++) {
const row = raw.subarray(y * stride, (y + 1) * stride);
row[0] = 0; // filter: none
row.fill(255, 1);
}
const ihdr = Buffer.alloc(13);
ihdr.writeUInt32BE(width, 0);
ihdr.writeUInt32BE(height, 4);
ihdr[8] = 8; // bit depth
ihdr[9] = 2; // color type: truecolor RGB
const compressed = zlib.deflateSync(raw);
const split = Math.max(1, Math.floor(compressed.length / 2));
return Buffer.concat([
PNG_SIG,
chunk("IHDR", ihdr),
chunk("IDAT", compressed.subarray(0, split)),
chunk("IDAT", compressed.subarray(split)),
chunk("IEND", Buffer.alloc(0)),
]);
}
// ─── the tests ───────────────────────────────────────────────────────────────
test("providerSafePng merges a Chrome-style split IDAT stream into ONE chunk, byte-identical stream", async () => {
const chromeStyle = chromeStyleMultiIdatPng();
const inChunks = walkChunks(chromeStyle);
assert.equal(
inChunks.filter((c) => c.type === "IDAT").length,
2,
"fixture sanity: the input is the multi-IDAT shape",
);
const fixed = providerSafePng(chromeStyle);
const outChunks = walkChunks(fixed);
const outIdats = outChunks.filter((c) => c.type === "IDAT");
assert.equal(outIdats.length, 1, "exactly one IDAT chunk — the form Alibaba's decoder accepts");
// The canonical property: the merged chunk's payload is the BYTE-IDENTICAL
// zlib stream (a transparent re-split, no re-encoding at all).
assert.ok(
outIdats[0]!.data.equals(Buffer.concat(inChunks.filter((c) => c.type === "IDAT").map((c) => c.data))),
"the zlib stream is untouched",
);
// Structure preserved: same non-IDAT chunks in order, valid CRCs (walkChunks
// is dumb, so verify CRC explicitly for the merged chunk).
assert.deepEqual(
outChunks.map((c) => c.type),
["IHDR", "IDAT", "IEND"],
);
const fixedBuf = fixed;
let off = 8;
while (off < fixedBuf.length) {
const len = fixedBuf.readUInt32BE(off);
const crc = fixedBuf.readUInt32BE(off + 8 + len);
assert.equal(crc, crc32(fixedBuf.subarray(off + 4, off + 8 + len)), `valid CRC on ${fixedBuf.subarray(off + 4, off + 8).toString("ascii")}`);
off += 12 + len;
}
// And it is still a decodable image: the stream inflates to EXACTLY the
// original scanlines (filter byte 0 + white RGB pixels).
const stream = walkChunks(fixed).find((c) => c.type === "IDAT")!.data;
const stride = 1 + 4 * 3;
const expected = Buffer.alloc(stride * 4);
for (let y = 0; y < 4; y++) {
const row = expected.subarray(y * stride, (y + 1) * stride);
row[0] = 0;
row.fill(255, 1);
}
assert.ok(zlib.inflateSync(stream).equals(expected), "inflates to the original raw scanlines");
});
test("a single-IDAT PNG is already canonical: returned byte-identical (no-op)", () => {
// One-IDAT input → one IDAT out, same bytes — the normal case (e.g. the
// doctrine-gate re-render of an already-canonical PNG).
const stride = 1 + 4 * 3;
const raw = Buffer.alloc(stride * 4);
for (let y = 0; y < 4; y++) {
const row = raw.subarray(y * stride, (y + 1) * stride);
row[0] = 0;
row.fill(200, 1);
}
const ihdr = Buffer.alloc(13);
ihdr.writeUInt32BE(4, 0);
ihdr.writeUInt32BE(4, 4);
ihdr[8] = 8;
ihdr[9] = 2;
const single = Buffer.concat([
PNG_SIG,
chunk("IHDR", ihdr),
chunk("IDAT", zlib.deflateSync(raw)),
chunk("IEND", Buffer.alloc(0)),
]);
assert.ok(providerSafePng(single).equals(single), "byte-identical — canonical input is untouched");
});
test("non-PNG or truncated input passes through unchanged (defensive)", () => {
const notPng = Buffer.from("definitely not a png");
assert.ok(providerSafePng(notPng).equals(notPng));
const truncated = chromeStyleMultiIdatPng().subarray(0, 20);
assert.ok(providerSafePng(truncated).equals(truncated));
});