mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-26 17:12:27 +03:00
* feat(compression): target-wire OmniGlyph stage and transport fidelity gate Roda o OmniGlyph depois da tradução para o wire real do provedor, em vez do corpo de origem. Um cliente OpenAI roteado para Claude deixava de comprimir com skip:source_format_not_claude porque o corpo ainda estava em formato OpenAI quando a engine era avaliada. - dispatch nativo por wire: Anthropic Messages, OpenAI Chat Completions e OpenAI Responses (input[] preservado, sem achatar para messages[]); - estágio target-wire pós-translateRequest, com guarda contra dupla compressão no caminho Claude→OpenAI; - preserveSystemPrompt do OmniRoute mapeado para compressSystem: false; - imageTransportPolicy: fidelidade de bytes/dimensões separada de supportsVision; só Anthropic/Claude tem recibo byte-preserving, o resto é fail-closed; - contagem de tokens de data URL PNG no wire OpenAI (marcador ;base64,); - README e i18n en/pt-BR com claims escopados ao caminho medido. * feat(compression): adota omniglyph 1.4.0 e tira o gate de modelo da env do host O 1.4.0 introduziu escopos de segurança e passou a resolvê-los dentro de isOmniGlyphSupportedModel() lendo process.env.OMNIGLYPH_PROFILE. Somado ao OMNIGLYPH_MODELS que já existia, duas variáveis do ambiente do host decidiam em silêncio o gate de TODO request do OmniRoute: passthrough desligaria a engine inteira e OMNIGLYPH_MODELS admitiria modelos sem recibo medido, enquanto a UI segue prometendo "Claude Fable 5 na rota direta medida". O adapter passa a usar isOmniGlyphSupportedModelForScope() com escopo explícito e fixa o escopo mais restrito como teto: a env só pode ESTREITAR a allowlist, nunca alargar. Os dois wires compartilham a mesma lista no pacote desde o 1.4.0, então uma checagem cobre Anthropic e GPT. - omniglyph ^1.3.1 -> ^1.4.0 (lock em 1.4.0); - testes de regressão para os dois caminhos de sequestro por env; - teste de contrato dos exports novos (escopo, perfis, accounting). O 1.4.0 também traz, sem mudança de código aqui: correção do glyph K que era lido como H, remoção do backtracking polinomial no secret-guard, overrides do pnpm em pnpm-workspace.yaml e as transitivas vulneráveis resolvidas. * feat(compression): expõe os perfis semânticos do omniglyph nos três wires O 1.4.0 trouxe perfis nomeados (coding-safe, balanced, aggressive, passthrough), mas só transformAnthropicMessages() os resolve sozinho: os transformadores OpenAI recebem TransformOptions cru e ignorariam o campo. Um perfil escolhido pelo operador valeria no wire Claude e sumiria no OpenAI. O adapter passa a mesclar o perfil com mergeCompressionProfileOptions() antes de chamar Chat Completions e Responses. O default segue aggressive — a política que os recibos publicados mediram. Medido nesta base: com coding-safe/balanced, uma sessão sem histórico acumulado para em below_min_chars e a engine não faz nada, porque os dois fixam minCompressChars no máximo e desligam system/tools/tool-results. Como a engine é opt-in, um default assim entregaria "ligado, 0% de ganho". O perfil é TETO, não piso: mergeCompressionProfileOptions não deixa um override do chamador reabrir uma lane lossy que o perfil fechou. Coberto por teste, por ser contra-intuitivo. Também fecha um caminho em que o OmniRoute violaria a própria política: o wire OpenAI do pacote não tem compressSystem — honra apenas compressTools, gptHistory, minCompressChars e reflow, e sempre troca a instrução por um ponteiro para a imagem. Com preserveSystemPrompt ligado, imagear assim queimaria o prefixo quente que a decisão cache-aware está protegendo, sem nada no corpo devolvido denunciando. A engine agora pula com skip:system_preservation_unsupported_on_wire. * feat(compression): contabilidade física do omniglyph com grau de evidência O adapter descartava o TransformInfo inteiro, então a UI mostrava um número de economia sem dizer de onde ele vinha — contagem do provider, estimativa ou só diferença de bytes. O 1.4.0 expõe normalizeAccounting(), que classifica essa evidência e resolve a semântica de cache por família: Anthropic reporta input, cache-create e cache-read em buckets DISJUNTOS, enquanto OpenAI e xAI reportam cached como SUBCONJUNTO do input. Somar à mão dá double-count silencioso. O novo omniglyphTelemetry.ts não filtra por denylist — MONTA um objeto novo, campo a campo, só com número e enum. TransformInfo mistura contadores inofensivos com material que não pode ser persistido: bytes PNG, imageSourceText(s), recoverable[].text, os sha8 de system/CLAUDE.md/primeira mensagem, nomes de tags observadas e o bloco env (cwd, branch, versões). Copiar o objeto inteiro transformaria telemetria de compressão em vazamento de prompt. O teste de negação prova que segredo, caminho do operador, texto do system e base64 não aparecem, e varre a allowlist exigindo que toda string seja de um enum conhecido. - provider threaded do chatCore e do bridge Codex WS até a engine; ausente vira `unknown`, que faz o upstream recusar adivinhar buckets de cache; - contabilidade propagada para o engineBreakdown do passo (o agregado do pipeline soma todas as engines e não serviria); - skip não emite contabilidade: zeros ali seriam indistinguíveis de "a engine nem rodou". * feat(compression): perfil do omniglyph configurável, persistido e documentado Fecha o caminho do operador: o perfil já existia no adapter, mas só como default de código. Agora atravessa schema Zod, normalizador do banco, API de settings e a página dedicada do engine. - OmniglyphConfig tipado + omniglyphConfigSchema (z.enum dos quatro perfis); - normalizeOmniglyphConfig: nome desconhecido vindo do storage cai para o default em vez de virar "roda com a política padrão"; - seletor na página do engine, com PATCH próprio — o perfil vive fora do mapa `engines`, e mandá-lo junto reescreveria o mapa inteiro (o store persiste o mapa como uma linha JSON só); - i18n en/pt-BR descrevendo o custo medido de cada perfil, não só o nome; - README e COMPRESSION_ENGINES.md com a regra do teto e o motivo de o default não ser o perfil mais seguro. Corrige de passagem um teste-irmão que ninguém via: o gate de transporte na UI deixou de dizer "direct Anthropic" quando os wires OpenAI nativos entraram, mas tests/unit/ui/omniglyphContextPage.test.tsx continuou afirmando a cópia antiga. O arquivo inteiro estava excluído do vitest.config.ts como "#8618 pre-existing failure", então a quebra passou silenciosa. Com a asserção alinhada o arquivo fecha 3/3, e a exclusão sai — o próprio comentário mandava removê-la quando corrigida. A doc não nomeia OMNIGLYPH_MODELS: o gate de docs fabricadas está certo em apontar que o OmniRoute nunca lê essa env — quem lê é o pacote. * fix(i18n): paridade do locale vi com as chaves novas do perfil do omniglyph `tests/unit/i18n-vi-completeness.test.ts` exige paridade ESTRITA de chaves entre en e vi — diferente do ratchet `i18n:check-ui-coverage`, que passa com 80%. As 11 chaves do seletor de perfil entraram só em en e pt-BR, e o gate de cobertura seguiu verde, então a quebra só apareceu na matriz completa do CI. --------- Co-authored-by: Xiangzhe <bakryun0718@proton.me> Co-authored-by: adevwithpurpose <adevwithpurpose@users.noreply.github.com>
196 lines
7.6 KiB
TypeScript
196 lines
7.6 KiB
TypeScript
import { test } from "node:test";
|
|
import assert from "node:assert";
|
|
import { applyCompressionAsync } from "../../../open-sse/services/compression/strategySelector.ts";
|
|
import { registerBuiltinCompressionEngines } from "../../../open-sse/services/compression/engines/index.ts";
|
|
|
|
const DENSE =
|
|
"X".repeat(500) +
|
|
"\n" +
|
|
Array.from(
|
|
{ length: 400 },
|
|
(_, i) => `const row_${i} = compute(${i * 17}, "${"v".repeat(80)}");`
|
|
).join("\n");
|
|
|
|
const body = () => ({
|
|
model: "claude-fable-5",
|
|
max_tokens: 128,
|
|
system: DENSE,
|
|
messages: [{ role: "user", content: [{ type: "text", text: "oi" }] }],
|
|
});
|
|
|
|
const openaiBody = () => ({
|
|
model: "gpt-5.6",
|
|
messages: [
|
|
{ role: "system", content: DENSE },
|
|
{ role: "user", content: "oi" },
|
|
],
|
|
});
|
|
|
|
const responsesBody = () => ({
|
|
model: "gpt-5.6",
|
|
instructions: DENSE,
|
|
input: [{ type: "message", role: "user", content: [{ type: "input_text", text: "oi" }] }],
|
|
});
|
|
|
|
// Agora que `estimateCompressionTokens` (stats.ts) é image-aware (Task 6), o guard
|
|
// honesto de inflação agregada do stacked (`guardPipelineInflation` em
|
|
// pipelineGuards.ts) não reverte mais a saída imageada do omniglyph: o estimador
|
|
// conta o bloco de imagem pelo billing real (patches 28px + overhead), não pelo
|
|
// char-count do base64. Este teste cobre tanto o plumbing de `providerTransport`
|
|
// (Task 5) quanto a saída final ponta a ponta (Task 6).
|
|
test("stacked com step omniglyph recebe providerTransport (engine roda, não é pulado por transporte)", async () => {
|
|
registerBuiltinCompressionEngines();
|
|
const r = await applyCompressionAsync(body(), "stacked", {
|
|
model: "claude-fable-5",
|
|
supportsVision: true,
|
|
providerTransport: "direct",
|
|
imageTransportFidelity: "byte-preserving",
|
|
config: { stackedPipeline: [{ engine: "rtk" }, { engine: "omniglyph" }] } as never,
|
|
});
|
|
const omniglyphStep = r.stats?.engineBreakdown?.find((e) => e.engine === "omniglyph");
|
|
assert.ok(omniglyphStep, "omniglyph step deveria aparecer no engineBreakdown");
|
|
assert.ok(
|
|
omniglyphStep!.techniquesUsed.includes("omniglyph:context-as-image"),
|
|
`omniglyph deveria ter rodado (não pulado) — techniquesUsed=${JSON.stringify(
|
|
omniglyphStep!.techniquesUsed
|
|
)}`
|
|
);
|
|
assert.equal(r.compressed, true);
|
|
assert.ok(
|
|
JSON.stringify(r.body).includes('"type":"image"'),
|
|
"stacked mantém a saída imageada (guard não reverte mais)"
|
|
);
|
|
});
|
|
|
|
test("stacked sem providerTransport 'direct' pula omniglyph (transport_not_direct)", async () => {
|
|
registerBuiltinCompressionEngines();
|
|
const r = await applyCompressionAsync(body(), "stacked", {
|
|
model: "claude-fable-5",
|
|
supportsVision: true,
|
|
// providerTransport ausente → fail-closed, omniglyph deve pular
|
|
config: { stackedPipeline: [{ engine: "rtk" }, { engine: "omniglyph" }] } as never,
|
|
});
|
|
const omniglyphStep = r.stats?.engineBreakdown?.find((e) => e.engine === "omniglyph");
|
|
assert.ok(omniglyphStep, "omniglyph step deveria aparecer no engineBreakdown mesmo pulado");
|
|
assert.ok(omniglyphStep!.techniquesUsed.includes("skip:transport_not_direct"));
|
|
});
|
|
|
|
test("stacked pós-tradução preserva o wire OpenAI Chat e executa OmniGlyph nativo", async () => {
|
|
registerBuiltinCompressionEngines();
|
|
const r = await applyCompressionAsync(openaiBody(), "stacked", {
|
|
model: "gpt-5.6",
|
|
supportsVision: true,
|
|
providerTransport: "direct",
|
|
imageTransportFidelity: "byte-preserving",
|
|
sourceFormat: "openai",
|
|
targetFormat: "openai",
|
|
compressionStage: "post-translation",
|
|
config: { stackedPipeline: [{ engine: "rtk" }, { engine: "omniglyph" }] } as never,
|
|
});
|
|
const omniglyphStep = r.stats?.engineBreakdown?.find((e) => e.engine === "omniglyph");
|
|
assert.ok(omniglyphStep);
|
|
assert.ok(omniglyphStep!.techniquesUsed.includes("omniglyph:context-as-image"));
|
|
assert.equal(r.compressed, true);
|
|
assert.ok(JSON.stringify(r.body).includes('"type":"image_url"'));
|
|
assert.ok(
|
|
r.stats?.validationWarnings?.some((warning) => warning.includes("rtk: skipped (stage")),
|
|
"engines sem suporte ao estágio devem ser explicitamente pulados"
|
|
);
|
|
});
|
|
|
|
test("stacked pós-tradução não adapta input[] Responses para messages[] antes do OmniGlyph", async () => {
|
|
registerBuiltinCompressionEngines();
|
|
const r = await applyCompressionAsync(responsesBody(), "stacked", {
|
|
model: "gpt-5.6",
|
|
supportsVision: true,
|
|
providerTransport: "direct",
|
|
imageTransportFidelity: "byte-preserving",
|
|
sourceFormat: "openai-responses",
|
|
targetFormat: "openai-responses",
|
|
compressionStage: "post-translation",
|
|
config: { stackedPipeline: [{ engine: "omniglyph" }] } as never,
|
|
});
|
|
assert.equal(r.compressed, true);
|
|
assert.ok(JSON.stringify(r.body).includes('"type":"input_image"'));
|
|
assert.ok(Array.isArray((r.body as { input?: unknown }).input));
|
|
});
|
|
|
|
test("OpenAI→Claude usa o wire traduzido sem uma segunda tradução", async () => {
|
|
registerBuiltinCompressionEngines();
|
|
const translatedClaudeBody = body();
|
|
const r = await applyCompressionAsync(translatedClaudeBody, "omniglyph", {
|
|
model: "claude-fable-5",
|
|
supportsVision: true,
|
|
providerTransport: "direct",
|
|
imageTransportFidelity: "byte-preserving",
|
|
sourceFormat: "openai",
|
|
targetFormat: "claude",
|
|
compressionStage: "post-translation",
|
|
});
|
|
assert.equal(r.compressed, true);
|
|
assert.ok(JSON.stringify(r.body).includes('"type":"image"'));
|
|
assert.ok(!JSON.stringify(r.body).includes('"type":"image_url"'));
|
|
});
|
|
|
|
test("Claude→OpenAI aguarda o wire alvo e não imageia o corpo fonte", async () => {
|
|
registerBuiltinCompressionEngines();
|
|
const pre = await applyCompressionAsync(body(), "omniglyph", {
|
|
model: "claude-fable-5",
|
|
supportsVision: true,
|
|
providerTransport: "direct",
|
|
imageTransportFidelity: "byte-preserving",
|
|
sourceFormat: "claude",
|
|
targetFormat: "openai",
|
|
compressionStage: "pre-translation",
|
|
});
|
|
assert.equal(pre.compressed, false);
|
|
assert.ok(pre.stats?.techniquesUsed.includes("skip:requires_post_translation"));
|
|
|
|
const post = await applyCompressionAsync(
|
|
{
|
|
model: "gpt-5.6",
|
|
messages: [
|
|
{ role: "system", content: DENSE },
|
|
{ role: "user", content: "oi" },
|
|
],
|
|
},
|
|
"omniglyph",
|
|
{
|
|
model: "gpt-5.6",
|
|
supportsVision: true,
|
|
providerTransport: "direct",
|
|
imageTransportFidelity: "byte-preserving",
|
|
sourceFormat: "claude",
|
|
targetFormat: "openai",
|
|
compressionStage: "post-translation",
|
|
}
|
|
);
|
|
assert.equal(post.compressed, true);
|
|
assert.ok(JSON.stringify(post.body).includes('"type":"image_url"'));
|
|
});
|
|
|
|
// O `provider` é threaded explicitamente por cada camada do selector (não é um
|
|
// spread cego), então uma camada nova pode derrubá-lo em silêncio: a
|
|
// contabilidade cairia para `unknown` e passaria a recusar a semântica de cache
|
|
// sem nenhum erro aparecer.
|
|
test("provider chega do selector até a contabilidade da engine", async () => {
|
|
registerBuiltinCompressionEngines();
|
|
const r = await applyCompressionAsync(body(), "stacked", {
|
|
model: "claude-fable-5",
|
|
provider: "anthropic",
|
|
supportsVision: true,
|
|
providerTransport: "direct",
|
|
imageTransportFidelity: "byte-preserving",
|
|
config: { stackedPipeline: [{ engine: "omniglyph" }] } as never,
|
|
});
|
|
assert.equal(r.compressed, true);
|
|
const omniglyphStep = r.stats?.engineBreakdown?.find((e) => e.engine === "omniglyph");
|
|
assert.ok(omniglyphStep, "omniglyph step deveria aparecer no engineBreakdown");
|
|
assert.equal(
|
|
omniglyphStep!.omniglyph?.provider,
|
|
"anthropic",
|
|
"sem o provider a contabilidade cai para unknown e recusa a semântica de cache"
|
|
);
|
|
assert.equal(omniglyphStep!.omniglyph?.savings.evidence, "bytes-only");
|
|
});
|