fix: updated OpenAI defaults and corrected catalog grouping behavior
- Resolved OpenAI shape resolution to `openai` and default to `8on16-bw`. - Fixed catalog generation to collapse effort tiers before provider grouping. - Updated help text and schemas to describe `8on16-bw` as the OpenAI auto default. - Added production `render_pages` and `mono_prod` scripts for end-to-end QA output.
This commit is contained in:
+1
-1
@@ -131,7 +131,7 @@ The automatic paths are intentionally different:
|
||||
|
||||
`compaction.strategy: "snapcompact"` replaces the LLM summarization call with a local, deterministic archival pass (`compact` from `@oh-my-pi/snapcompact`):
|
||||
|
||||
- The discarded history is serialized, whitespace-collapsed, and printed onto provider-aware square PNG frames using bundled public-domain pixel fonts. Anthropic-family and unknown APIs use repeated black `8x8` cells, Google uses repeated sentence-colored `8x8` cells, and OpenAI uses dense stretched `6x6` cells with `detail: "original"`. The `snapcompact.shape` setting (default `auto`) forces one of the research-eval variants instead: square grids (`8x8r`/`8x8u`/`6x6u`/`5x8` × sentence-hue/black ink) or the per-model eval winners (`6x12-dim`, `8x13-bw`, `8on16-bw`, and the two-column word-wrapped `doc-8on16-bw`/`-sent`/`-sent-dim`, where `dim` prints stopwords in gray). A forced variant keeps its geometry but is re-priced for the target provider's image billing. The same setting governs inline system-prompt/tool-result imaging (`snapcompact.systemPrompt`, `snapcompact.toolResults`).
|
||||
- The discarded history is serialized, whitespace-collapsed, and printed onto provider-aware square PNG frames using bundled public-domain pixel fonts. Anthropic-family and unknown APIs use repeated black `8x8` cells, Google uses repeated sentence-colored `8x8` cells, and OpenAI uses `8x13` glyphs on a 16px pitch (`8on16-bw`) with `detail: "original"`. The `snapcompact.shape` setting (default `auto`) forces one of the research-eval variants instead: square grids (`8x8r`/`8x8u`/`6x6u`/`5x8` × sentence-hue/black ink) or the per-model eval winners (`6x12-dim`, `8x13-bw`, `8on16-bw`, and the two-column word-wrapped `doc-8on16-bw`/`-sent`/`-sent-dim`, where `dim` prints stopwords in gray). A forced variant keeps its geometry but is re-priced for the target provider's image billing. The same setting governs inline system-prompt/tool-result imaging (`snapcompact.systemPrompt`, `snapcompact.toolResults`).
|
||||
- Serialization keeps the archive conversation-dense: tool results are truncated head+tail (default 2,000 chars at a 0.6 head ratio), tool-call argument values are capped per value (500) and per call (2,000), and tool output is printed in dim gray ink so conversation reads louder than tool noise. All budgets and the dimming are configurable via `SerializeOptions` (`toolResultMaxChars`, `toolArgMaxChars`, `toolCallMaxChars`, `truncateHeadRatio`, `dimToolResults`).
|
||||
- Frames persist under `CompactionEntry.preserveData.snapcompact` and are re-attached to the `compactionSummary` message as image blocks on every context rebuild; the entry's `summary` is a deterministic reading guide (grid geometry, role tags, truncation notes) plus the usual file-operation lists.
|
||||
- Later compactions carry earlier frames forward. Beyond an 8-frame budget the archive fades from the middle out: the earliest frame (session head — the original request, or the filmed summary of older history) is pinned, and the oldest *unpinned* frames are evicted, so head and tail both survive. If the previous compaction was text-based, its summary is printed at the head of the frame archive as `[Summary of earlier history]`.
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# Changelog
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- Added effort-tier variant collapsing (`variant-collapse`): providers that expose one logical model as several effort/thinking-suffixed upstream ids (Antigravity CCA `gemini-3.5-flash-extra-low`/`-low`/`gemini-3-flash-agent`, `gemini-3[.1]-pro-low|high`, `claude-*[-thinking]` pairs, `gpt-oss-120b-medium`) collapse into one logical entry carrying per-effort upstream routing in `thinking.effortRouting` (plus `thinking.suppressWhenOff` for Cloud Code Assist ids whose baked server default re-applies when `thinkingConfig` is omitted). Request-time code resolves the outbound id via `resolveWireModelId(model, effort)`; selection, caching, and usage attribution key on the logical id.
|
||||
@@ -15,6 +14,10 @@
|
||||
- Changed `gemini-2.5-flash-thinking` handling from discovery-denylist to collapsing into `gemini-2.5-flash` (thinking-enabled requests route to the `-thinking` backing id)
|
||||
- Bumped the model cache schema to v5 so rows predating effort-tier variant collapsing (raw `-low`/`-high`/`-thinking` member ids) are invalidated
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed catalog generation to apply effort-tier variant collapsing before provider grouping to ensure collapsed model families are consistently materialized without being impacted by in-loop mutation
|
||||
|
||||
## [15.11.4] - 2026-06-12
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -474,6 +474,10 @@ async function generateModels() {
|
||||
});
|
||||
applyGeneratedModelPolicies(allModels);
|
||||
linkOpenAIPromotionTargets(allModels);
|
||||
// Collapse effort-tier variants AFTER the policy re-bake: live-discovery
|
||||
// entries are already collapsed (rebake skips them); this pass folds
|
||||
// previous-snapshot raw members into their logical families.
|
||||
allModels = collapseEffortVariantsAcrossProviders(allModels);
|
||||
|
||||
// Group by provider and sort each provider's models
|
||||
const providers: Record<string, Record<string, ModelSpec>> = {};
|
||||
@@ -481,10 +485,6 @@ async function generateModels() {
|
||||
if (DISCOVERY_ONLY_PROVIDERS.has(model.provider)) continue;
|
||||
if (!providers[model.provider]) {
|
||||
providers[model.provider] = {};
|
||||
// Collapse effort-tier variants AFTER the policy re-bake: live-discovery
|
||||
// entries are already collapsed (rebake skips them); this pass folds
|
||||
// previous-snapshot raw members into their logical families.
|
||||
allModels = collapseEffortVariantsAcrossProviders(allModels);
|
||||
}
|
||||
// Use model ID as key to automatically deduplicate
|
||||
// Only add if not already present (models.dev takes priority over endpoint discovery)
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
# Changelog
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- Added `snapcompact.shape` setting to pick the frame variant snapcompact prints text with — `auto` (each provider's eval winner) or any research-eval variant: the square grids (`8x8r`/`8x8u`/`6x6u`/`5x8` × sentence-hue/black ink) and the per-model winners (`6x12-dim`, `8x13-bw`, `8on16-bw`, `doc-8on16-bw`, `doc-8on16-sent`, `doc-8on16-sent-dim`); applies to both the compaction archive and inline system-prompt/tool-result imaging
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated `snapcompact.shape` option help text so the `auto` mode and `8on16-bw` descriptions now reflect OpenAI’s current default and GPT winner variants
|
||||
- Model selectors keyed by retired effort-tier variant ids keep resolving after catalog collapsing: reference resolution and bare-id matching consult the hand-table aliases (`gemini-3.5-flash-low`, `gemini-pro-agent`, recycled `gemini-3-flash`) plus the `X-thinking` → `X` grammar for auto-collapsed pairs, with exact matches always winning while a raw id is live; explicit `:effort` suffixes transfer unchanged. models.yml `modelOverrides` and rate-limit selector suppressions keyed by raw member ids re-key onto the collapsed model
|
||||
- Custom/config provider model lists now collapse `X`/`X-thinking` twins at registry rebuild (`collapseBuiltModelVariants`), folding config-defined twins into one entry whose thinking toggle routes to the `-thinking` backing id
|
||||
|
||||
|
||||
@@ -1624,7 +1624,7 @@ export const SETTINGS_SCHEMA = {
|
||||
{
|
||||
value: "auto",
|
||||
label: "Auto",
|
||||
description: "Provider's eval winner: 8x8r-bw on Anthropic, 8x8r-sent on Gemini, 6x6u-sent on OpenAI.",
|
||||
description: "Provider's eval winner: 8x8r-bw on Anthropic, 8x8r-sent on Gemini, 8on16-bw on OpenAI.",
|
||||
},
|
||||
{
|
||||
value: "8x8r-bw",
|
||||
@@ -1680,7 +1680,8 @@ export const SETTINGS_SCHEMA = {
|
||||
{
|
||||
value: "8on16-bw",
|
||||
label: "8x13 on 16px pitch, black",
|
||||
description: "8x13 glyphs on an 8x16 cell (extra leading), black ink. GPT grid runner-up (.906).",
|
||||
description:
|
||||
"8x13 glyphs on an 8x16 cell (extra leading), black ink. GPT eval winner (chunked .906, mono .851) and the OpenAI auto default.",
|
||||
},
|
||||
{
|
||||
value: "doc-8on16-bw",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
### Changed
|
||||
|
||||
- **Changed the OpenAI default shape from `6x6u-sent` to `8on16-bw`.** A production-regime mono eval (gpt-5.5, the full 800k-char SQuAD flow in one request, n=50) scored the old dense default f1 .602 vs .851 for `8on16-bw` rendered by the production pipeline, at near-equal total cost (the dense cells burned the frame savings on reasoning tokens); chunked exp14 had already scored `8on16-bw` .906. `SHAPES.openaiDense` is renamed to `SHAPES.openai`
|
||||
- `normalize()` now keeps line structure: whitespace runs containing a line break collapse to `NEWLINE_GLYPH` (U+2588 FULL BLOCK, drawn by the native renderer as a pitch-black cell one character wide) instead of a plain space; leading/trailing breaks are trimmed, and the frame-reading prompt explains the marker
|
||||
- `normalize()` now skips characters the fonts cannot render instead of printing `?` blanks: whole ANSI escape sequences are stripped, and bare control characters, zero-width format characters (ZWSP, BOM, directional marks), combining marks, and lone surrogates are dropped without occupying a cell; `?` remains the fallback for unsupported graphic characters only
|
||||
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
# /// script
|
||||
# requires-python = ">=3.10"
|
||||
# dependencies = ["pillow"]
|
||||
# ///
|
||||
"""Mono probe over PRODUCTION-rendered snapcompact frames.
|
||||
|
||||
Same protocol as mono.py (one request carries the whole 800k-char SQuAD flow,
|
||||
questions sampled across the span, seed 42), but the frames come from the
|
||||
shipping TypeScript/Rust pipeline via render_pages.ts instead of the research
|
||||
PIL renderer. Validates a candidate production shape end-to-end.
|
||||
|
||||
uv run mono_prod.py --shape doc-8on16-bw
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
sys.path.insert(0, str(HERE))
|
||||
|
||||
import squad # noqa: E402
|
||||
from final import MODELS, cached # noqa: E402
|
||||
from providers import llm_complete, load_env_key # noqa: E402
|
||||
from run import CACHE, RESULTS, load_prompt, sha8 # noqa: E402
|
||||
|
||||
SIZE = 1568
|
||||
|
||||
# Production Shape payloads, keyed by variant name (geometry only; billing
|
||||
# fields are required by isShape but irrelevant to rendering).
|
||||
SHAPES = {
|
||||
"doc-8on16-bw": {"font": "8x13", "cellWidth": 8, "cellHeight": 16, "stretch": False, "variant": "bw",
|
||||
"columns": 2, "lineRepeat": 1, "frameSize": SIZE, "frameTokenEstimate": 2900},
|
||||
"doc-8on16-sent": {"font": "8x13", "cellWidth": 8, "cellHeight": 16, "stretch": False, "variant": "sent",
|
||||
"columns": 2, "lineRepeat": 1, "frameSize": SIZE, "frameTokenEstimate": 2900},
|
||||
"doc-8on16-sent-dim": {"font": "8x13", "cellWidth": 8, "cellHeight": 16, "stretch": False, "variant": "sent",
|
||||
"columns": 2, "stopwordDim": True, "lineRepeat": 1, "frameSize": SIZE,
|
||||
"frameTokenEstimate": 2900},
|
||||
"8on16-bw": {"font": "8x13", "cellWidth": 8, "cellHeight": 16, "stretch": False, "variant": "bw",
|
||||
"lineRepeat": 1, "frameSize": SIZE, "frameTokenEstimate": 2900},
|
||||
"6x12-dim": {"font": "6x12", "cellWidth": 6, "cellHeight": 12, "variant": "bw", "stopwordDim": True,
|
||||
"lineRepeat": 1, "frameSize": SIZE, "frameTokenEstimate": 3300},
|
||||
"8x13-bw": {"font": "8x13", "cellWidth": 8, "cellHeight": 13, "variant": "bw",
|
||||
"lineRepeat": 1, "frameSize": SIZE, "frameTokenEstimate": 3300},
|
||||
"8x8r-bw": {"font": "8x8", "cellWidth": 8, "cellHeight": 8, "variant": "bw",
|
||||
"lineRepeat": 2, "frameSize": SIZE, "frameTokenEstimate": 3300},
|
||||
"8x8r-sent": {"font": "8x8", "cellWidth": 8, "cellHeight": 8, "variant": "sent",
|
||||
"lineRepeat": 2, "frameSize": SIZE, "frameTokenEstimate": 1100},
|
||||
}
|
||||
|
||||
|
||||
def main() -> None:
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--shape", required=True, choices=sorted(SHAPES))
|
||||
ap.add_argument("--model", default="gpt-5.5")
|
||||
ap.add_argument("--chars", type=int, default=800_000)
|
||||
ap.add_argument("--questions", type=int, default=50)
|
||||
ap.add_argument("--qpb", type=int, default=5)
|
||||
ap.add_argument("--seed", type=int, default=42)
|
||||
ap.add_argument("--max-tokens", type=int, default=32768)
|
||||
ap.add_argument("--effort", default=None)
|
||||
ap.add_argument("--env", default="~/.env")
|
||||
ap.add_argument("--fresh", action="store_true")
|
||||
args = ap.parse_args()
|
||||
|
||||
keys = {
|
||||
"anthropic": load_env_key("ANTHROPIC_API_KEY", args.env),
|
||||
"openai": load_env_key("OPENAI_API_KEY", args.env),
|
||||
"openrouter": load_env_key("OPENROUTER_API_KEY", args.env),
|
||||
}
|
||||
paras = squad.load_paragraphs(CACHE)
|
||||
flow, offsets = squad.build_flow(paras, args.chars)
|
||||
questions = squad.sample_chunk_questions(paras, offsets, 0, len(flow), args.questions, args.seed)
|
||||
price_in, price_out = MODELS[args.model]
|
||||
cond = f"prod-{args.shape}"
|
||||
|
||||
# Production frames (keyed by flow + shape so corpus changes re-render).
|
||||
shape = SHAPES[args.shape]
|
||||
frame_dir = CACHE / f"prod-frames-{args.shape}-{sha8(flow, json.dumps(shape, sort_keys=True))}"
|
||||
if not frame_dir.exists() or not any(frame_dir.iterdir()):
|
||||
flow_file = CACHE / f"prod-flow-{sha8(flow)}.txt"
|
||||
flow_file.write_text(flow)
|
||||
subprocess.run(
|
||||
["bun", str(HERE / "render_pages.ts"), str(flow_file), json.dumps(shape), str(frame_dir)],
|
||||
check=True,
|
||||
)
|
||||
pngs = sorted(frame_dir.glob("page-*.png"))
|
||||
repeat = shape.get("lineRepeat", 1)
|
||||
cols = (SIZE // shape["cellWidth"] - 3) // 2 if shape.get("columns") == 2 else SIZE // shape["cellWidth"]
|
||||
rows = SIZE // shape["cellHeight"] // repeat
|
||||
preamble = load_prompt("qa-image-multi.md").format(k=len(pngs), cols=cols, rows=rows)
|
||||
if shape.get("columns") == 2:
|
||||
preamble += (
|
||||
"\nNote: each image lays text out as two word-wrapped newspaper columns separated by a gutter; "
|
||||
"read the left column top to bottom, then the right column."
|
||||
)
|
||||
if repeat > 1:
|
||||
preamble += (
|
||||
f"\nNote: every text line is rendered {repeat} times consecutively - first on the plain "
|
||||
"background, then repeated on a pale highlight band. The copies show identical characters; "
|
||||
"cross-check between them when a glyph is hard to read, and do not treat copies as separate text."
|
||||
)
|
||||
ctx_blocks = [{"text": preamble}, *({"image_path": p} for p in pngs), {"text": "End of images.", "cache": True}]
|
||||
|
||||
out_dir = RESULTS / f"mono-prod-{args.model.replace('/', '-')}-{args.shape}"
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
answers, usages, stops = [], [], []
|
||||
for b in range(0, len(questions), args.qpb):
|
||||
batch = questions[b : b + args.qpb]
|
||||
q_block = "\n".join(f"{i + 1}. {q['q']}" for i, q in enumerate(batch))
|
||||
messages = [{"role": "user", "content": [*ctx_blocks, {"text": q_block}]}]
|
||||
qa = cached(
|
||||
args.model, "qa-mono-prod", {"messages": messages, "effort": args.effort},
|
||||
lambda m=messages: dict(
|
||||
zip(
|
||||
("text", "usage", "stop"),
|
||||
llm_complete(keys, args.model, m, max_tokens=args.max_tokens, effort=args.effort),
|
||||
)
|
||||
),
|
||||
args.fresh,
|
||||
)
|
||||
answers.extend(squad.parse_numbered(qa["text"], len(batch)))
|
||||
usages.append(qa["usage"])
|
||||
stops.append(qa["stop"])
|
||||
rows_out = [
|
||||
{
|
||||
"model": args.model, "cond": cond, "pos_rel": q["pos_rel"], "q": q["q"], "answer": a,
|
||||
"golds": q["golds"], "em": squad.exact_match(a, q["golds"]), "f1": squad.f1(a, q["golds"]),
|
||||
"abstained": "unreadable" in a.lower(),
|
||||
}
|
||||
for q, a in zip(questions, answers)
|
||||
]
|
||||
u = {k: sum(x[k] for x in usages) for k in ("in", "out", "cache_w", "cache_r", "reasoning")}
|
||||
cost = (u["in"] + 1.25 * u["cache_w"] + 0.1 * u["cache_r"]) / 1e6 * price_in + u["out"] / 1e6 * price_out
|
||||
quart = []
|
||||
for lo, hi in ((0, 0.25), (0.25, 0.5), (0.5, 0.75), (0.75, 1.01)):
|
||||
qs = [r["f1"] for r in rows_out if lo <= r["pos_rel"] < hi]
|
||||
quart.append(sum(qs) / len(qs) if qs else float("nan"))
|
||||
summary = {
|
||||
"cond": cond, "n": len(rows_out), "imgs": len(pngs),
|
||||
"em": sum(r["em"] for r in rows_out) / len(rows_out),
|
||||
"f1": sum(r["f1"] for r in rows_out) / len(rows_out),
|
||||
"abst": sum(r["abstained"] for r in rows_out),
|
||||
"tok_in": u["in"], "tok_cached": u["cache_r"], "tok_out": u["out"], "reas": u["reasoning"],
|
||||
"cost": cost, "stop": next((s for s in stops if s == "max_tokens"), stops[-1] if stops else ""),
|
||||
"q1": quart[0], "q2": quart[1], "q3": quart[2], "q4": quart[3],
|
||||
}
|
||||
(out_dir / "records.jsonl").write_text("\n".join(json.dumps(r) for r in rows_out))
|
||||
(out_dir / "summary.json").write_text(json.dumps([summary], indent=1))
|
||||
print(
|
||||
f"{cond:<22} imgs={summary['imgs']:>2} f1={summary['f1']:.3f} em={summary['em']:.3f} "
|
||||
f"abst={summary['abst']} ${summary['cost']:.2f} stop={summary['stop']}"
|
||||
)
|
||||
print("F1 by quartile: " + " ".join(f"q{i + 1}={v:.3f}" for i, v in enumerate(quart)))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Render EVERY page of a text flow with the production snapcompact renderer.
|
||||
*
|
||||
* Usage: bun render_pages.ts <text-file> <shape-json> <out-dir>
|
||||
*
|
||||
* Writes <out-dir>/page-000.png … and prints the page count. Drives the exact
|
||||
* shipping pipeline (renderMany: wrap, pagination, stopword dimming).
|
||||
*/
|
||||
import * as fs from "node:fs/promises";
|
||||
import * as path from "node:path";
|
||||
import * as snapcompact from "../src/snapcompact";
|
||||
|
||||
const [textFile, shapeJson, outDir] = Bun.argv.slice(2);
|
||||
if (!textFile || !shapeJson || !outDir) {
|
||||
throw new Error("usage: bun render_pages.ts <text-file> <shape-json> <out-dir>");
|
||||
}
|
||||
|
||||
const text = await Bun.file(textFile).text();
|
||||
const shape = JSON.parse(shapeJson) as snapcompact.Shape;
|
||||
if (!snapcompact.isShape(shape)) {
|
||||
throw new Error(`shape json is not a complete Shape: ${shapeJson}`);
|
||||
}
|
||||
|
||||
await fs.mkdir(outDir, { recursive: true });
|
||||
const frames = snapcompact.renderMany(text, { shape });
|
||||
for (let i = 0; i < frames.length; i++) {
|
||||
await Bun.write(path.join(outDir, `page-${String(i).padStart(3, "0")}.png`), Buffer.from(frames[i].data, "base64"));
|
||||
}
|
||||
console.log(frames.length);
|
||||
@@ -206,8 +206,11 @@ export const SHAPES = {
|
||||
anthropic: priceShape(SHAPE_VARIANTS["8x8r-bw"], "anthropic"),
|
||||
/** `8x8r-sent`: the repeated grid with sentence-hue ink. */
|
||||
google: priceShape(SHAPE_VARIANTS["8x8r-sent"], "google"),
|
||||
/** `6x6u-sent`: unscii stretched to 6x6 — densest readable cell, fewest frames. */
|
||||
openaiDense: priceShape(SHAPE_VARIANTS["6x6u-sent"], "openai"),
|
||||
/** `8on16-bw`: 8x13 X.org glyphs on a 16px pitch, black ink. Mono eval on
|
||||
* gpt-5.5 (200k-token single request, n=50): f1 .851 vs .602 for the
|
||||
* previous `6x6u-sent` default at near-equal total cost; chunked exp14
|
||||
* scored it .906. */
|
||||
openai: priceShape(SHAPE_VARIANTS["8on16-bw"], "openai"),
|
||||
/** Original 5x8 X.org shape (pre-shape-table sessions rendered this). */
|
||||
legacy: priceShape(SHAPE_VARIANTS["5x8-sent"], "anthropic"),
|
||||
} satisfies Record<string, Shape>;
|
||||
@@ -249,7 +252,7 @@ export function resolveShape(api?: Api, variant?: ShapeVariantName | "auto"): Sh
|
||||
if (variant && variant !== "auto") return priceShape(SHAPE_VARIANTS[variant], family);
|
||||
switch (family) {
|
||||
case "openai":
|
||||
return SHAPES.openaiDense;
|
||||
return SHAPES.openai;
|
||||
case "google":
|
||||
return SHAPES.google;
|
||||
default:
|
||||
|
||||
@@ -158,8 +158,8 @@ describe("normalize", () => {
|
||||
describe("shape resolution", () => {
|
||||
it("maps provider APIs to their eval-winning shapes", () => {
|
||||
expect(snapcompact.resolveShape("anthropic-messages")).toBe(snapcompact.SHAPES.anthropic);
|
||||
expect(snapcompact.resolveShape("openai-responses")).toBe(snapcompact.SHAPES.openaiDense);
|
||||
expect(snapcompact.resolveShape("azure-openai-responses")).toBe(snapcompact.SHAPES.openaiDense);
|
||||
expect(snapcompact.resolveShape("openai-responses")).toBe(snapcompact.SHAPES.openai);
|
||||
expect(snapcompact.resolveShape("azure-openai-responses")).toBe(snapcompact.SHAPES.openai);
|
||||
expect(snapcompact.resolveShape("google-generative-ai")).toBe(snapcompact.SHAPES.google);
|
||||
// Unknown and absent APIs fall back to the refusal-robust plain shape.
|
||||
expect(snapcompact.resolveShape("some-future-api")).toBe(snapcompact.SHAPES.anthropic);
|
||||
@@ -179,7 +179,7 @@ describe("shape resolution", () => {
|
||||
|
||||
const repeatedOnOpenai = snapcompact.resolveShape("openai-responses", "8x8r-bw");
|
||||
expect(repeatedOnOpenai.lineRepeat).toBe(2);
|
||||
expect(repeatedOnOpenai.frameTokenEstimate).toBe(snapcompact.SHAPES.openaiDense.frameTokenEstimate);
|
||||
expect(repeatedOnOpenai.frameTokenEstimate).toBe(snapcompact.SHAPES.openai.frameTokenEstimate);
|
||||
expect(repeatedOnOpenai.imageDetail).toBe("original");
|
||||
|
||||
// Legacy 2576px frames keep the conservative ceiling on every provider.
|
||||
@@ -200,10 +200,10 @@ describe("shape resolution", () => {
|
||||
});
|
||||
|
||||
it("recognizes complete shape overrides and rejects malformed ones", () => {
|
||||
expect(snapcompact.isShape(snapcompact.SHAPES.openaiDense)).toBe(true);
|
||||
expect(snapcompact.isShape({ ...snapcompact.SHAPES.openaiDense, cellWidth: 0 })).toBe(false);
|
||||
expect(snapcompact.isShape({ ...snapcompact.SHAPES.openaiDense, variant: "color" })).toBe(false);
|
||||
expect(snapcompact.isShape({ ...snapcompact.SHAPES.openaiDense, imageDetail: "original" })).toBe(true);
|
||||
expect(snapcompact.isShape(snapcompact.SHAPES.openai)).toBe(true);
|
||||
expect(snapcompact.isShape({ ...snapcompact.SHAPES.openai, cellWidth: 0 })).toBe(false);
|
||||
expect(snapcompact.isShape({ ...snapcompact.SHAPES.openai, variant: "color" })).toBe(false);
|
||||
expect(snapcompact.isShape({ ...snapcompact.SHAPES.openai, imageDetail: "original" })).toBe(true);
|
||||
});
|
||||
|
||||
it("images forwards the per-frame detail hint", () => {
|
||||
@@ -260,8 +260,9 @@ describe("render", () => {
|
||||
expect(used.has(1)).toBe(false); // no sentence hues in bw
|
||||
});
|
||||
|
||||
it("renders the openai stretch shape as truecolor RGB", () => {
|
||||
const frame = snapcompact.render("Hello world.", snapcompact.SHAPES.openaiDense, TEST_FRAME_SIZE);
|
||||
it("renders a stretched shape as truecolor RGB", () => {
|
||||
const stretched = snapcompact.resolveShape("openai-responses", "6x6u-sent");
|
||||
const frame = snapcompact.render("Hello world.", stretched, TEST_FRAME_SIZE);
|
||||
// IHDR color type byte: 2 = truecolor RGB (anti-aliased stretch output).
|
||||
expect(Buffer.from(frame.data, "base64")[25]).toBe(2);
|
||||
expect(frame.cols).toBe(Math.floor(TEST_FRAME_SIZE / 6));
|
||||
@@ -315,7 +316,7 @@ describe("renderMany", () => {
|
||||
});
|
||||
|
||||
it("honors maxFrames and propagates the shape's detail hint", () => {
|
||||
const shape = snapcompact.SHAPES.openaiDense;
|
||||
const shape = snapcompact.SHAPES.openai;
|
||||
const { capacity } = snapcompact.geometry(shape, TEST_FRAME_SIZE);
|
||||
const frames = snapcompact.renderMany("x".repeat(capacity * 3), {
|
||||
shape,
|
||||
@@ -323,7 +324,7 @@ describe("renderMany", () => {
|
||||
maxFrames: 2,
|
||||
});
|
||||
expect(frames).toHaveLength(2);
|
||||
// openaiDense carries imageDetail: "original"; anthropic carries none.
|
||||
// The openai shape carries imageDetail: "original"; anthropic carries none.
|
||||
expect(frames[0].detail).toBe("original");
|
||||
const bw = snapcompact.renderMany("hi", { shape: snapcompact.SHAPES.anthropic, frameSize: TEST_FRAME_SIZE });
|
||||
expect(bw[0].detail).toBeUndefined();
|
||||
|
||||
Reference in New Issue
Block a user