mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-18 13:14:56 +03:00
Compare commits
6 Commits
release/v3
...
feat/omnig
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
19e7bd6d63 | ||
|
|
098c72d1e3 | ||
|
|
638fe783a0 | ||
|
|
b5e5838a59 | ||
|
|
ce89143232 | ||
|
|
6b05eedc8d |
@@ -853,7 +853,7 @@ Engines run in pipeline order; each is independently toggleable and configurable
|
||||
<tr><td align="center" nowrap>9</td><td align="left" nowrap><b>Aggressive</b></td><td align="left">Summarization + progressive aging of old turns</td></tr>
|
||||
<tr><td align="center" nowrap>10</td><td align="left" nowrap><b>LLMLingua-2</b></td><td align="left">ML semantic pruning via MobileBERT ONNX — code-safe, async</td></tr>
|
||||
<tr><td align="center" nowrap>11</td><td align="left" nowrap><b>Ultra</b></td><td align="left">Heuristic token pruning with an optional small-model (SLM) tier</td></tr>
|
||||
<tr><td align="center" nowrap>12</td><td align="left" nowrap><b>OmniGlyph</b></td><td align="left">Experimental context-as-image encoding routed to Claude Fable 5 (most aggressive; opt-in)</td></tr>
|
||||
<tr><td align="center" nowrap>12</td><td align="left" nowrap><b>OmniGlyph</b></td><td align="left">Experimental context-as-image encoding for measured Claude Fable 5 on the direct Anthropic wire; GPT 5.6 transformers remain fail-closed pending provider receipts. Four compression profiles (aggressive default, balanced, coding-safe, passthrough) (most aggressive; opt-in)</td></tr>
|
||||
</table>
|
||||
|
||||
Code blocks, URLs and structured data are **always preserved** byte-perfect. **One-click presets** combine the engines:
|
||||
|
||||
@@ -19,8 +19,36 @@ OmniRoute compression is built around engine contracts. A mode can run one engin
|
||||
| `aggressive` | Caveman + history/tool summarizers | Long chat sessions |
|
||||
| `ultra` | Caveman + pruning helpers | Context-limit recovery |
|
||||
| `rtk` | RTK | Terminal, shell, build, test, and git output |
|
||||
| `omniglyph` | OmniGlyph | Context-as-image on the native provider wire |
|
||||
| `stacked` | Pipeline, default `rtk -> caveman` | Mixed tool logs and prose, max savings |
|
||||
|
||||
### OmniGlyph compression profiles
|
||||
|
||||
The `omniglyph` engine (package `omniglyph`, 1.4.0+) accepts a named semantic profile, set
|
||||
globally through `omniglyph.profile` in the compression settings or per step through the
|
||||
stacked pipeline's step config:
|
||||
|
||||
| Profile | Boundary |
|
||||
| -------------- | --------------------------------------------------------------------------- |
|
||||
| `aggressive` | Default. The policy the published receipts measured — images system, tool docs and dense history |
|
||||
| `balanced` | Keeps live state native, protects the last 8 turns, collapses older closed history |
|
||||
| `coding-safe` | Keeps authority, tool schemas and live tool output native, protects the last 12 turns |
|
||||
| `passthrough` | Routes without transforming; the engine is skipped |
|
||||
|
||||
The profile is a **ceiling, not a floor**: `mergeCompressionProfileOptions` in the package
|
||||
refuses to let a caller override reopen a lossy lane the profile closed, so a per-step
|
||||
`preserveSystemPrompt: false` cannot re-enable system compression under `coding-safe`.
|
||||
|
||||
Measured on this codebase: `coding-safe` and `balanced` raise `minCompressChars` to its
|
||||
maximum and keep system, tool schemas and tool results native, so a session that has not
|
||||
accumulated history yet stops at `below_min_chars` and the engine transforms nothing. That
|
||||
is why the default is `aggressive` rather than the safest profile.
|
||||
|
||||
The package resolves its own model scope and profile from its environment configuration.
|
||||
OmniRoute never delegates the decision: the adapter pins the model gate to the package's
|
||||
most restrictive scope, so host environment settings can only narrow the allowlist, never
|
||||
widen it past OmniRoute's measured receipts.
|
||||
|
||||
## Engine Registry
|
||||
|
||||
The registry lives in `open-sse/services/compression/engines/registry.ts`. Engines expose a shared
|
||||
|
||||
@@ -88,6 +88,7 @@ import { checkResourcePressureGuard } from "../utils/resourcePressure.ts";
|
||||
import { normalizeHeaders } from "../utils/headers.ts";
|
||||
import { resolveChatCoreRequestFormat } from "./chatCore/requestFormat.ts";
|
||||
import { resolveChatCoreTargetFormat } from "./chatCore/targetFormat.ts";
|
||||
import { resolveOmniGlyphTransport } from "../services/compression/imageTransportPolicy.ts";
|
||||
import { stripStore, usesClaudeBridge } from "./chatCore/agentRouterProtocol.ts";
|
||||
import { defaultClaudeToolType } from "./chatCore/claudeToolDefaults.ts";
|
||||
import { injectSystemPrompt, injectCustomSystemPrompt } from "../services/systemPrompt.ts";
|
||||
@@ -1548,16 +1549,13 @@ export async function handleChatCore({
|
||||
// models, which is intentionally NOT `false` so the gate still preserves images.
|
||||
supportsVision: getResolvedModelCapabilities({ provider, model: effectiveModel })
|
||||
.supportsVision,
|
||||
// Rotas diretas oficiais ('anthropic' API key e 'claude' OAuth) vs agregadores:
|
||||
// o engine omniglyph exige 'direct' — agregadores redimensionam imagens
|
||||
// (medido 2026-07-06). OAuth 'claude' é rota direta oficial (#7863).
|
||||
providerTransport:
|
||||
provider === "anthropic" ||
|
||||
provider === "claude" ||
|
||||
provider === "openai" ||
|
||||
provider === "xai"
|
||||
? ("direct" as const)
|
||||
: ("aggregator" as const),
|
||||
// OmniGlyph uses a measured provider/image-fidelity allowlist. Direct HTTP
|
||||
// alone is not proof that a route preserves PNG bytes and dimensions.
|
||||
...resolveOmniGlyphTransport(provider),
|
||||
// Sem o provider, a contabilidade do OmniGlyph cai para `unknown` e
|
||||
// recusa deduzir a semântica de cache (Anthropic usa buckets disjuntos,
|
||||
// OpenAI reporta cached como subconjunto do input).
|
||||
provider,
|
||||
sourceFormat,
|
||||
targetFormat,
|
||||
compressionStage: "pre-translation" as const,
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
* - modelo fora da allowlist medida → skip:model_not_approved
|
||||
* - providerTransport !== 'direct' → skip:transport_not_direct
|
||||
* (agregadores redimensionam imagens e destroem a legibilidade — medido)
|
||||
* - imageTransportFidelity !== 'byte-preserving' → skip:transport_fidelity_unknown/resizes
|
||||
* - wire não é Claude/OpenAI suportado → skip:target_format_not_supported
|
||||
* - gate de rentabilidade interno do omniglyph decide o resto (patches 28px
|
||||
* exatos; texto esparso/pequeno passa direto) → skip:not_profitable
|
||||
@@ -20,14 +21,65 @@ import type { CompressionEngine, CompressionEngineApplyOptions } from "./types.t
|
||||
import type { CompressionResult } from "../types.ts";
|
||||
import { createCompressionStats } from "../stats.ts";
|
||||
import {
|
||||
isOmniGlyphSupportedGptModel,
|
||||
isOmniGlyphSupportedModel,
|
||||
buildOmniGlyphAccounting,
|
||||
type OmniGlyphAccounting,
|
||||
} from "../omniglyphTelemetry.ts";
|
||||
import {
|
||||
isOmniGlyphSupportedModelForScope,
|
||||
mergeCompressionProfileOptions,
|
||||
resolveCompressionProfile,
|
||||
transformAnthropicMessages,
|
||||
transformOpenAIChatCompletions,
|
||||
transformOpenAIResponses,
|
||||
type CompressionProfile,
|
||||
type OmniGlyphSafetyScope,
|
||||
} from "omniglyph";
|
||||
import { isModelImageable } from "omniglyph/applicability";
|
||||
|
||||
/**
|
||||
* Teto de modelos do OmniRoute — sempre o escopo mais restrito do pacote.
|
||||
*
|
||||
* `isOmniGlyphSupportedModel()` resolve o escopo lendo `OMNIGLYPH_PROFILE` do
|
||||
* processo, e a lista base sai de `OMNIGLYPH_MODELS`. Duas variáveis do HOST
|
||||
* decidiriam, 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 continua prometendo "Claude Fable 5 na rota
|
||||
* direta medida". Fixar o escopo mais restrito faz o gate só poder ESTREITAR
|
||||
* pela env, nunca alargar, e mantém a decisão na configuração do OmniRoute.
|
||||
*/
|
||||
const MEASURED_MODEL_SCOPE: OmniGlyphSafetyScope = "coding-safe";
|
||||
|
||||
/**
|
||||
* Perfil padrão do OmniRoute.
|
||||
*
|
||||
* `aggressive` é a política que os recibos publicados mediram. `coding-safe` e
|
||||
* `balanced` fixam `minCompressChars` no máximo e só colapsam histórico antigo:
|
||||
* medido nesta base, uma sessão sem histórico acumulado fica em
|
||||
* `below_min_chars` e a engine não faz nada — o operador veria "ligado, 0% de
|
||||
* ganho". Ficam disponíveis como escolha explícita, não como default.
|
||||
*/
|
||||
const DEFAULT_PROFILE: OmniGlyphSafetyScope = "aggressive";
|
||||
|
||||
/** Perfil do passo (mais específico) > perfil global > default do OmniRoute. */
|
||||
function resolveProfileName(options?: CompressionEngineApplyOptions): string {
|
||||
const step = options?.stepConfig?.profile;
|
||||
if (typeof step === "string" && step.trim()) return step;
|
||||
const global = (options?.config as { omniglyph?: { profile?: unknown } } | undefined)?.omniglyph
|
||||
?.profile;
|
||||
if (typeof global === "string" && global.trim()) return global;
|
||||
return DEFAULT_PROFILE;
|
||||
}
|
||||
|
||||
/**
|
||||
* O modelo precisa passar no teto medido E no escopo em vigor. Os dois wires
|
||||
* (Anthropic e GPT) compartilham a mesma allowlist no pacote desde 1.4.0, então
|
||||
* uma única checagem cobre os dois.
|
||||
*/
|
||||
function isModelWithinScope(model: string, scope: OmniGlyphSafetyScope): boolean {
|
||||
if (!isOmniGlyphSupportedModelForScope(model, MEASURED_MODEL_SCOPE)) return false;
|
||||
return isOmniGlyphSupportedModelForScope(model, scope);
|
||||
}
|
||||
|
||||
function skip(body: Record<string, unknown>, reason: string): CompressionResult {
|
||||
try {
|
||||
return {
|
||||
@@ -80,9 +132,36 @@ async function applyOmniglyph(
|
||||
const model = options?.model ?? (body as { model?: string }).model ?? "";
|
||||
if (options?.supportsVision !== true) return skip(body, "no_vision");
|
||||
if (options?.providerTransport !== "direct") return skip(body, "transport_not_direct");
|
||||
// Keep the old direct-call contract usable for standalone callers, but let
|
||||
// production callers override it explicitly. The chat pipeline supplies
|
||||
// `unknown` for every provider without a byte-preservation receipt.
|
||||
if (
|
||||
options?.imageTransportFidelity !== undefined &&
|
||||
options.imageTransportFidelity !== "byte-preserving"
|
||||
) {
|
||||
return skip(
|
||||
body,
|
||||
options.imageTransportFidelity === "resizes"
|
||||
? "transport_resizes_images"
|
||||
: "transport_fidelity_unknown"
|
||||
);
|
||||
}
|
||||
|
||||
const stage = options?.compressionStage ?? "pre-translation";
|
||||
const wireFormat = resolveWireFormat(body, options);
|
||||
// A source/target format mismatch means the body is still on the wrong wire,
|
||||
// even when the source itself is native Claude. Defer the engine until the
|
||||
// translated provider body so Claude→OpenAI cannot be imaged once before
|
||||
// translation and then considered again on the target wire.
|
||||
const sourceWireFormat = options?.sourceFormat ?? wireFormat;
|
||||
if (
|
||||
stage === "pre-translation" &&
|
||||
options?.targetFormat &&
|
||||
sourceWireFormat &&
|
||||
options.targetFormat !== sourceWireFormat
|
||||
) {
|
||||
return skip(body, "requires_post_translation");
|
||||
}
|
||||
// The pre-translation lane is retained for the existing native Claude
|
||||
// passthrough. OpenAI requests must wait until translation has produced the
|
||||
// exact provider wire, otherwise Responses input[] would be flattened by the
|
||||
@@ -104,53 +183,98 @@ async function applyOmniglyph(
|
||||
) {
|
||||
return skip(body, "source_format_not_openai_responses");
|
||||
}
|
||||
const supportedModel =
|
||||
wireFormat === "claude"
|
||||
? isOmniGlyphSupportedModel(model)
|
||||
: isOmniGlyphSupportedGptModel(model);
|
||||
if (!supportedModel) return skip(body, "model_not_approved");
|
||||
let profile: CompressionProfile;
|
||||
try {
|
||||
profile = resolveCompressionProfile(resolveProfileName(options));
|
||||
} catch {
|
||||
// `resolveCompressionProfile` lança em nome desconhecido. Um perfil que o
|
||||
// pacote não entende não pode virar "roda com a política padrão".
|
||||
return skip(body, "invalid_profile");
|
||||
}
|
||||
if (profile.name === "passthrough") return skip(body, "profile_passthrough");
|
||||
if (!isModelWithinScope(model, profile.name)) {
|
||||
return skip(body, "model_not_approved");
|
||||
}
|
||||
const preserveSystemPrompt =
|
||||
(typeof options?.stepConfig?.preserveSystemPrompt === "boolean"
|
||||
? options.stepConfig.preserveSystemPrompt
|
||||
: options?.config?.preserveSystemPrompt) === true;
|
||||
// `compressSystem` só existe no transform Anthropic. Os wires OpenAI honram
|
||||
// apenas compressTools/gptHistory/minCompressChars/reflow e sempre trocam a
|
||||
// instrução por um ponteiro para a imagem. Imagear o system quando o OmniRoute
|
||||
// decidiu preservá-lo queimaria o prefixo quente que a política cache-aware
|
||||
// está protegendo — e nada no corpo devolvido denunciaria isso. Sem como
|
||||
// honrar a política nesse wire, a engine pula.
|
||||
if (preserveSystemPrompt && wireFormat !== "claude") {
|
||||
return skip(body, "system_preservation_unsupported_on_wire");
|
||||
}
|
||||
// OmniGlyph 1.3.x deliberately keeps unverified families (currently Grok)
|
||||
// text-only until the operator acknowledges them via its own env gate.
|
||||
if (!isModelImageable(model)) return skip(body, "model_not_imageable");
|
||||
|
||||
const started = Date.now();
|
||||
let outBody: Record<string, unknown>;
|
||||
let accounting: OmniGlyphAccounting | undefined;
|
||||
try {
|
||||
const encoded = new TextEncoder().encode(JSON.stringify(body));
|
||||
// The upstream OpenAI transformer resolves its billing/render profile from
|
||||
// body.model. Keep the provider body byte-compatible on output, but use the
|
||||
// already-resolved engine model for that internal gate when a translator
|
||||
// omitted the model or left an alias in place.
|
||||
const transformBody =
|
||||
wireFormat !== "claude" && model && body.model !== model ? { ...body, model } : body;
|
||||
const encoded = new TextEncoder().encode(JSON.stringify(transformBody));
|
||||
const overrides = preserveSystemPrompt ? { compressSystem: false } : {};
|
||||
// Só `transformAnthropicMessages` resolve o perfil por conta própria; os
|
||||
// transformadores OpenAI recebem TransformOptions cru e ignorariam o campo.
|
||||
const openAIOptions = mergeCompressionProfileOptions(profile, overrides);
|
||||
const result =
|
||||
wireFormat === "claude"
|
||||
? await transformAnthropicMessages({ body: encoded, model })
|
||||
? await transformAnthropicMessages({
|
||||
body: encoded,
|
||||
model,
|
||||
options: { ...overrides, profile: profile.name },
|
||||
})
|
||||
: wireFormat === "openai"
|
||||
? await transformOpenAIChatCompletions(encoded)
|
||||
: await transformOpenAIResponses(encoded);
|
||||
const applied = wireFormat === "claude" ? result.applied : result.info.compressed;
|
||||
? await transformOpenAIChatCompletions(encoded, openAIOptions)
|
||||
: await transformOpenAIResponses(encoded, openAIOptions);
|
||||
const applied = "applied" in result ? result.applied : result.info.compressed;
|
||||
if (!applied) return skip(body, result.info?.reason ?? "not_profitable");
|
||||
outBody = JSON.parse(new TextDecoder().decode(result.body)) as Record<string, unknown>;
|
||||
if (transformBody !== body && body.model !== undefined) outBody.model = body.model;
|
||||
accounting = buildOmniGlyphAccounting({
|
||||
provider: options?.provider,
|
||||
model,
|
||||
originalBytes: encoded.byteLength,
|
||||
transformedBytes: result.body.byteLength,
|
||||
info: result.info,
|
||||
durationMs: Date.now() - started,
|
||||
});
|
||||
} catch {
|
||||
// Fail-open: qualquer erro no encode/transform/decode (ex.: corpo não serializável,
|
||||
// render PNG estourando, JSON decodificado malformado) vira skip, nunca propaga.
|
||||
return skip(body, "transform_error");
|
||||
}
|
||||
|
||||
return {
|
||||
body: outBody,
|
||||
compressed: true,
|
||||
stats: createCompressionStats(
|
||||
body,
|
||||
outBody,
|
||||
"stacked",
|
||||
["omniglyph:context-as-image"],
|
||||
undefined,
|
||||
Date.now() - started
|
||||
),
|
||||
};
|
||||
const stats = createCompressionStats(
|
||||
body,
|
||||
outBody,
|
||||
"stacked",
|
||||
["omniglyph:context-as-image"],
|
||||
undefined,
|
||||
Date.now() - started
|
||||
);
|
||||
// A contabilidade só acompanha uma conversão que realmente aconteceu: um skip
|
||||
// não tem economia para reportar, e inventar zeros ali viraria "0% de ganho"
|
||||
// indistinguível de "a engine nem rodou".
|
||||
if (accounting) stats.omniglyph = accounting;
|
||||
|
||||
return { body: outBody, compressed: true, stats };
|
||||
}
|
||||
|
||||
export const omniglyphEngine: CompressionEngine = {
|
||||
id: "omniglyph",
|
||||
name: "OmniGlyph",
|
||||
description:
|
||||
"Contexto-como-imagem (Anthropic Fable 5, rota direta): system prompt, tool docs e histórico viram páginas PNG densas — ~10× menos tokens no bloco convertido.",
|
||||
"Contexto-como-imagem para Claude Fable 5 na rota direta medida; wires GPT nativos ficam disponíveis apenas após recibo de fidelidade do provedor.",
|
||||
icon: "image",
|
||||
targets: ["messages", "tool_results"],
|
||||
stackable: true,
|
||||
@@ -160,7 +284,7 @@ export const omniglyphEngine: CompressionEngine = {
|
||||
id: "omniglyph",
|
||||
name: "OmniGlyph",
|
||||
description:
|
||||
"Contexto-como-imagem para Claude Fable 5 e GPT 5.6 via wires nativos Anthropic/OpenAI em rota direta.",
|
||||
"Contexto-como-imagem para Claude Fable 5 na rota direta medida; transformadores GPT nativos permanecem fail-closed até validação do provedor.",
|
||||
inputScope: "mixed",
|
||||
targetLatencyMs: 250, // render+encode PNG de páginas grandes
|
||||
supportsPreview: true,
|
||||
|
||||
@@ -7,6 +7,9 @@ export type CompressionWireFormat = "claude" | "openai" | "openai-responses" | s
|
||||
|
||||
export type CompressionStage = "pre-translation" | "post-translation";
|
||||
|
||||
/** Whether an upstream route preserves OmniGlyph PNG bytes and dimensions. */
|
||||
export type ImageTransportFidelity = "byte-preserving" | "resizes" | "unknown";
|
||||
|
||||
export interface EngineConfigField {
|
||||
key: string;
|
||||
type: "boolean" | "number" | "string" | "select" | "multiselect";
|
||||
@@ -42,8 +45,11 @@ export interface CompressionEngineApplyOptions {
|
||||
/** Como o request chega ao provider: rota direta oficial ('direct') vs
|
||||
* agregador que pode reprocessar imagens ('aggregator'). O engine omniglyph
|
||||
* exige 'direct' — medição 2026-07-06: agregadores redimensionam as páginas
|
||||
* e destroem a legibilidade. undefined = desconhecido = skip (fail-closed). */
|
||||
* e destroem a legibilidade. A política de produção também informa
|
||||
* imageTransportFidelity; chamadas legadas sem esse campo mantêm o gate direct. */
|
||||
providerTransport?: "direct" | "aggregator";
|
||||
/** Independent image-fidelity gate; direct HTTP does not imply byte preservation. */
|
||||
imageTransportFidelity?: ImageTransportFidelity;
|
||||
/** Protocol shape before the current compression stage. */
|
||||
sourceFormat?: CompressionWireFormat;
|
||||
/** Protocol shape expected by the upstream provider. */
|
||||
@@ -55,6 +61,10 @@ export interface CompressionEngineApplyOptions {
|
||||
stepConfig?: Record<string, unknown>;
|
||||
/** Authenticated principal (API key id) making the request. Used by CCR to scope its store. */
|
||||
principalId?: string;
|
||||
/** Provider resolvido do alvo. A contabilidade do omniglyph depende dele:
|
||||
* Anthropic reporta input/cache em buckets disjuntos, OpenAI/xAI reportam
|
||||
* cached como subconjunto do input. Ausente => `unknown` (falha fechado). */
|
||||
provider?: string;
|
||||
}
|
||||
|
||||
export interface CompressionEngine {
|
||||
|
||||
33
open-sse/services/compression/imageTransportPolicy.ts
Normal file
33
open-sse/services/compression/imageTransportPolicy.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Provider-level image transport policy for loss-sensitive compression engines.
|
||||
*
|
||||
* `supportsVision` only says that a model can read images. OmniGlyph also needs
|
||||
* the PNG bytes and dimensions to survive the provider route unchanged. The
|
||||
* allowlist below contains only paths with an existing OmniRoute receipt;
|
||||
* everything else is deliberately classified as unknown and skipped.
|
||||
*/
|
||||
|
||||
import type { ImageTransportFidelity } from "./engines/types.ts";
|
||||
|
||||
export type OmniGlyphTransportPolicy = {
|
||||
providerTransport: "direct" | "aggregator";
|
||||
imageTransportFidelity: ImageTransportFidelity;
|
||||
};
|
||||
|
||||
const BYTE_PRESERVING_PROVIDERS = new Set(["anthropic", "claude"]);
|
||||
|
||||
export function resolveOmniGlyphTransport(
|
||||
provider: string | null | undefined
|
||||
): OmniGlyphTransportPolicy {
|
||||
const normalized = typeof provider === "string" ? provider.trim().toLowerCase() : "";
|
||||
if (BYTE_PRESERVING_PROVIDERS.has(normalized)) {
|
||||
return {
|
||||
providerTransport: "direct",
|
||||
imageTransportFidelity: "byte-preserving",
|
||||
};
|
||||
}
|
||||
return {
|
||||
providerTransport: "aggregator",
|
||||
imageTransportFidelity: "unknown",
|
||||
};
|
||||
}
|
||||
156
open-sse/services/compression/omniglyphTelemetry.ts
Normal file
156
open-sse/services/compression/omniglyphTelemetry.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
/**
|
||||
* Ponte de telemetria do OmniGlyph — allowlist positiva.
|
||||
*
|
||||
* `TransformInfo` mistura contadores inofensivos com material que NUNCA pode
|
||||
* ser persistido: bytes PNG, `imageSourceText(s)`, `recoverable[].text`, os
|
||||
* sha8 de system/CLAUDE.md/primeira mensagem, os nomes de tags observadas e o
|
||||
* bloco `env` (cwd, branch, versões). Copiar o objeto inteiro seria transformar
|
||||
* a telemetria de compressão num vazamento do prompt.
|
||||
*
|
||||
* Este módulo não filtra por denylist — ele MONTA um objeto novo, campo a
|
||||
* campo, só com número e enum. Um campo novo no upstream não entra sozinho.
|
||||
*
|
||||
* `normalizeAccounting()` (OmniGlyph 1.4.0) faz a parte difícil: classifica o
|
||||
* grau de evidência da economia e resolve a semântica de cache por provider —
|
||||
* Anthropic reporta input/cache-create/cache-read em buckets DISJUNTOS,
|
||||
* enquanto OpenAI e xAI reportam `cached` como SUBCONJUNTO do input. Somar à
|
||||
* mão dá double-count silencioso.
|
||||
*/
|
||||
|
||||
import {
|
||||
normalizeAccounting,
|
||||
type AccountingProvider,
|
||||
type OmniGlyphTransformInfo,
|
||||
type SavingsEvidence,
|
||||
} from "omniglyph";
|
||||
|
||||
/** Contabilidade segura de uma execução do OmniGlyph. Só número e enum. */
|
||||
export interface OmniGlyphAccounting {
|
||||
provider: AccountingProvider;
|
||||
model?: string;
|
||||
bytes: {
|
||||
original?: number;
|
||||
transformed?: number;
|
||||
reduced?: number;
|
||||
compressionRatio?: number;
|
||||
};
|
||||
tokens: {
|
||||
estimatedOriginalInput?: number;
|
||||
estimatedActualInput?: number;
|
||||
estimatedReduced?: number;
|
||||
image?: number;
|
||||
};
|
||||
savings: {
|
||||
/** De onde saiu o número: contagem do provider, estimativa ou só bytes. */
|
||||
evidence: SavingsEvidence;
|
||||
inputTokensReduced?: number;
|
||||
inputReductionRatio?: number;
|
||||
};
|
||||
images: {
|
||||
count: number;
|
||||
bytes: number;
|
||||
pixels?: number;
|
||||
};
|
||||
/** Chars de origem imageados vs. mantidos como texto por turno. */
|
||||
chars: {
|
||||
original?: number;
|
||||
imaged?: number;
|
||||
static?: number;
|
||||
dynamic?: number;
|
||||
outgoingText?: number;
|
||||
};
|
||||
dynamicBlockCount?: number;
|
||||
latencyMs?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* A semântica de cache de `normalizeAccounting` depende da família do provider,
|
||||
* não do nome comercial da rota. Rota desconhecida vira `unknown`, que faz o
|
||||
* upstream falhar fechado em vez de adivinhar buckets de cache.
|
||||
*/
|
||||
export function toAccountingProvider(provider: string | null | undefined): AccountingProvider {
|
||||
const normalized = typeof provider === "string" ? provider.trim().toLowerCase() : "";
|
||||
if (normalized === "anthropic" || normalized === "claude") return "anthropic";
|
||||
if (normalized === "openai" || normalized === "codex" || normalized === "chatgpt") {
|
||||
return "openai";
|
||||
}
|
||||
if (normalized === "xai" || normalized === "grok") return "xai";
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
function count(value: unknown): number | undefined {
|
||||
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : undefined;
|
||||
}
|
||||
|
||||
export function buildOmniGlyphAccounting(params: {
|
||||
provider: string | null | undefined;
|
||||
model?: string;
|
||||
originalBytes: number;
|
||||
transformedBytes: number;
|
||||
info?: OmniGlyphTransformInfo | null;
|
||||
durationMs?: number;
|
||||
}): OmniGlyphAccounting {
|
||||
const { info } = params;
|
||||
const provider = toAccountingProvider(params.provider);
|
||||
|
||||
// `baselineImagedTokens` é o custo em tokens de texto do que foi imageado (o
|
||||
// "teria pago assim"); `imageTokens` é o que as imagens custam de fato. Os
|
||||
// dois só existem no wire GPT — no Anthropic a evidência honesta cai para
|
||||
// bytes, e é isso que o campo `evidence` passa a dizer em vez de exibir um
|
||||
// número sem procedência.
|
||||
const normalized = normalizeAccounting({
|
||||
provider,
|
||||
...(params.model ? { model: params.model } : {}),
|
||||
originalBytes: params.originalBytes,
|
||||
transformedBytes: params.transformedBytes,
|
||||
...(count(info?.baselineImagedTokens) !== undefined
|
||||
? { estimatedOriginalInputTokens: info!.baselineImagedTokens }
|
||||
: {}),
|
||||
...(count(info?.imageTokens) !== undefined
|
||||
? { estimatedTransformedInputTokens: info!.imageTokens }
|
||||
: {}),
|
||||
...(count(info?.imageTokens) !== undefined ? { imageTokens: info!.imageTokens } : {}),
|
||||
...(params.durationMs !== undefined ? { proxyAddedLatencyMs: params.durationMs } : {}),
|
||||
});
|
||||
|
||||
const chars = {
|
||||
...(count(info?.origChars) !== undefined ? { original: info!.origChars } : {}),
|
||||
...(count(info?.compressedChars) !== undefined ? { imaged: info!.compressedChars } : {}),
|
||||
...(count(info?.staticChars) !== undefined ? { static: info!.staticChars } : {}),
|
||||
...(count(info?.dynamicChars) !== undefined ? { dynamic: info!.dynamicChars } : {}),
|
||||
...(count(info?.outgoingTextChars) !== undefined
|
||||
? { outgoingText: info!.outgoingTextChars }
|
||||
: {}),
|
||||
};
|
||||
|
||||
return {
|
||||
provider: normalized.provider,
|
||||
...(normalized.model ? { model: normalized.model } : {}),
|
||||
bytes: normalized.bytes,
|
||||
tokens: {
|
||||
...(normalized.tokens.estimatedOriginalInput !== undefined
|
||||
? { estimatedOriginalInput: normalized.tokens.estimatedOriginalInput }
|
||||
: {}),
|
||||
...(normalized.tokens.estimatedActualInput !== undefined
|
||||
? { estimatedActualInput: normalized.tokens.estimatedActualInput }
|
||||
: {}),
|
||||
...(normalized.tokens.estimatedReduced !== undefined
|
||||
? { estimatedReduced: normalized.tokens.estimatedReduced }
|
||||
: {}),
|
||||
...(normalized.tokens.image !== undefined ? { image: normalized.tokens.image } : {}),
|
||||
},
|
||||
savings: normalized.savings,
|
||||
images: {
|
||||
count: count(info?.imageCount) ?? 0,
|
||||
bytes: count(info?.imageBytes) ?? 0,
|
||||
...(count(info?.imagePixels) !== undefined ? { pixels: info!.imagePixels } : {}),
|
||||
},
|
||||
chars,
|
||||
...(count(info?.dynamicBlockCount) !== undefined
|
||||
? { dynamicBlockCount: info!.dynamicBlockCount }
|
||||
: {}),
|
||||
...(normalized.latency.proxyAddedMs !== undefined
|
||||
? { latencyMs: normalized.latency.proxyAddedMs }
|
||||
: {}),
|
||||
};
|
||||
}
|
||||
@@ -110,5 +110,8 @@ export function mergeStackStep(
|
||||
techniquesUsed: result.stats.techniquesUsed,
|
||||
...(result.stats.rulesApplied ? { rulesApplied: result.stats.rulesApplied } : {}),
|
||||
...(result.stats.durationMs !== undefined ? { durationMs: result.stats.durationMs } : {}),
|
||||
// O agregado do pipeline soma tokens de todas as engines; a contabilidade
|
||||
// física do omniglyph só faz sentido no passo que a produziu.
|
||||
...(result.stats.omniglyph ? { omniglyph: result.stats.omniglyph } : {}),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ function isOpenAIResponsesPngImagePart(value: unknown): value is OpenAIResponses
|
||||
}
|
||||
|
||||
function pngDimensionsFromDataUrl(value: string): { width: number; height: number } | null {
|
||||
const marker = ",base64,";
|
||||
const marker = ";base64,";
|
||||
const markerIndex = value.indexOf(marker);
|
||||
if (markerIndex < 0) return null;
|
||||
return decodePngDimensions(value.slice(markerIndex + marker.length));
|
||||
|
||||
@@ -11,6 +11,7 @@ import type {
|
||||
CompressionEngineApplyOptions,
|
||||
CompressionStage,
|
||||
CompressionWireFormat,
|
||||
ImageTransportFidelity,
|
||||
} from "./engines/types.ts";
|
||||
import { applyLiteCompression } from "./lite.ts";
|
||||
import { cavemanCompress } from "./caveman.ts";
|
||||
@@ -266,6 +267,7 @@ export function applyCompression(
|
||||
options?: {
|
||||
model?: string;
|
||||
supportsVision?: boolean | null;
|
||||
imageTransportFidelity?: ImageTransportFidelity;
|
||||
sourceFormat?: CompressionWireFormat;
|
||||
targetFormat?: CompressionWireFormat;
|
||||
compressionStage?: CompressionStage;
|
||||
@@ -292,6 +294,7 @@ function runCompression(
|
||||
options?: {
|
||||
model?: string;
|
||||
supportsVision?: boolean | null;
|
||||
imageTransportFidelity?: ImageTransportFidelity;
|
||||
sourceFormat?: CompressionWireFormat;
|
||||
targetFormat?: CompressionWireFormat;
|
||||
compressionStage?: CompressionStage;
|
||||
@@ -479,6 +482,9 @@ export async function applyCompressionAsync(
|
||||
supportsVision?: boolean | null;
|
||||
/** Direct-to-provider vs. aggregator transport (gates transport-sensitive engines like omniglyph). */
|
||||
providerTransport?: "direct" | "aggregator";
|
||||
/** Provider resolvido — a contabilidade do omniglyph depende dele. */
|
||||
provider?: string;
|
||||
imageTransportFidelity?: ImageTransportFidelity;
|
||||
sourceFormat?: CompressionWireFormat;
|
||||
targetFormat?: CompressionWireFormat;
|
||||
compressionStage?: CompressionStage;
|
||||
@@ -501,6 +507,9 @@ async function runCompressionAsync(
|
||||
supportsVision?: boolean | null;
|
||||
/** Direct-to-provider vs. aggregator transport (gates transport-sensitive engines like omniglyph). */
|
||||
providerTransport?: "direct" | "aggregator";
|
||||
/** Provider resolvido — a contabilidade do omniglyph depende dele. */
|
||||
provider?: string;
|
||||
imageTransportFidelity?: ImageTransportFidelity;
|
||||
sourceFormat?: CompressionWireFormat;
|
||||
targetFormat?: CompressionWireFormat;
|
||||
compressionStage?: CompressionStage;
|
||||
@@ -693,6 +702,9 @@ interface StackOptions {
|
||||
supportsVision?: boolean | null;
|
||||
/** Direct-to-provider vs. aggregator transport (gates transport-sensitive engines like omniglyph). */
|
||||
providerTransport?: "direct" | "aggregator";
|
||||
/** Provider resolvido — a contabilidade do omniglyph depende dele. */
|
||||
provider?: string;
|
||||
imageTransportFidelity?: ImageTransportFidelity;
|
||||
sourceFormat?: CompressionWireFormat;
|
||||
targetFormat?: CompressionWireFormat;
|
||||
compressionStage?: CompressionStage;
|
||||
|
||||
@@ -16,6 +16,7 @@ import type { RiskGateConfig } from "./riskGate/riskGate.ts";
|
||||
import type { PipelineCircuitBreakerConfig } from "./pipelineEngineBreaker.ts";
|
||||
import type { RiskGateStats } from "./riskGate/riskGateStep.ts";
|
||||
import type { QuantumLockConfig, QuantumLockStats } from "./quantumLock/quantumPatterns.ts";
|
||||
import type { OmniGlyphAccounting } from "./omniglyphTelemetry.ts";
|
||||
|
||||
// Re-export so consumers that already import from this module (e.g. src/lib/db/compression.ts)
|
||||
// can get ENGINE_IDS without a second bare `@omniroute/open-sse/...engineCatalog.ts` specifier.
|
||||
@@ -157,6 +158,22 @@ export interface LiveZoneConfig {
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
/** Perfil semântico do OmniGlyph (pacote 1.4.0+). */
|
||||
export type OmniglyphProfile = "coding-safe" | "balanced" | "aggressive" | "passthrough";
|
||||
|
||||
/**
|
||||
* Política do OmniGlyph escolhida pelo operador.
|
||||
*
|
||||
* O perfil é um TETO: `mergeCompressionProfileOptions` do pacote não deixa um
|
||||
* override reabrir uma lane que o perfil fechou. Trocar de `aggressive` para
|
||||
* `coding-safe` mantém system, schemas de tools e tool results nativos, ao custo
|
||||
* medido de a engine não fazer nada até a sessão acumular histórico
|
||||
* (`minCompressChars` vai ao máximo). Por isso o default é `aggressive`.
|
||||
*/
|
||||
export interface OmniglyphConfig {
|
||||
profile: OmniglyphProfile;
|
||||
}
|
||||
|
||||
/** Lite detail settings for proactive request-time transformations. */
|
||||
export interface LiteConfig {
|
||||
/** Truncate tool-result strings over 2,000 characters before provider dispatch. */
|
||||
@@ -206,6 +223,8 @@ export interface CompressionConfig {
|
||||
comboOverrides: Record<string, CompressionMode>;
|
||||
compressionComboId?: string | null;
|
||||
stackedPipeline?: CompressionPipelineStep[];
|
||||
/** Política do engine OmniGlyph (perfil semântico). */
|
||||
omniglyph?: OmniglyphConfig;
|
||||
/** Opt-in QuantumLock cache-prefix stabilization (default off). */
|
||||
quantumLock?: QuantumLockConfig;
|
||||
/** Opt-in per-step fidelity gate (default disabled). */
|
||||
@@ -303,6 +322,12 @@ export interface CompressionStats {
|
||||
validationWarnings?: string[];
|
||||
validationErrors?: string[];
|
||||
fallbackApplied?: boolean;
|
||||
/**
|
||||
* Contabilidade física do OmniGlyph, normalizada pelo próprio pacote
|
||||
* (`normalizeAccounting`). Só número e enum — ver `omniglyphTelemetry.ts`
|
||||
* para a allowlist e o que nunca pode entrar aqui.
|
||||
*/
|
||||
omniglyph?: OmniGlyphAccounting;
|
||||
riskGate?: RiskGateStats;
|
||||
/**
|
||||
* Phase 4 (B): which `ultra` tier actually ran for this request.
|
||||
@@ -341,6 +366,8 @@ export interface CompressionStats {
|
||||
durationMs?: number;
|
||||
rejected?: boolean;
|
||||
rejectReason?: string;
|
||||
/** Contabilidade física — presente só no passo omniglyph que comprimiu. */
|
||||
omniglyph?: OmniGlyphAccounting;
|
||||
}>;
|
||||
/** Present only when QuantumLock stabilized ≥1 fragment this run. */
|
||||
quantumLock?: QuantumLockStats;
|
||||
@@ -460,6 +487,16 @@ export const DEFAULT_COMPRESSION_LANGUAGE_CONFIG: CompressionLanguageConfig = {
|
||||
enabledPacks: ["en"],
|
||||
};
|
||||
|
||||
/**
|
||||
* `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 — como o OmniGlyph é opt-in, esse
|
||||
* default entregaria "ligado, 0% de ganho".
|
||||
*/
|
||||
export const DEFAULT_OMNIGLYPH_CONFIG: OmniglyphConfig = {
|
||||
profile: "aggressive",
|
||||
};
|
||||
|
||||
export const DEFAULT_CONTEXT_EDITING_CONFIG: ContextEditingConfig = {
|
||||
enabled: false,
|
||||
};
|
||||
|
||||
8
package-lock.json
generated
8
package-lock.json
generated
@@ -60,7 +60,7 @@
|
||||
"next-intl": "^4.13.6",
|
||||
"next-themes": "^0.4.6",
|
||||
"node-machine-id": "^1.1.12",
|
||||
"omniglyph": "^1.0.2",
|
||||
"omniglyph": "^1.4.0",
|
||||
"onnxruntime-node": "~1.24.3",
|
||||
"open": "^11.0.0",
|
||||
"ora": "^9.4.1",
|
||||
@@ -28871,9 +28871,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/omniglyph": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/omniglyph/-/omniglyph-1.3.1.tgz",
|
||||
"integrity": "sha512-6QnZCoXYczjsPN2x+XpbimimjO6kCoSZUzsdSvoKjtw28U1U724VgLICBNaLX4FFs5jd7SrYNNs9Aee2iIkcoA==",
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/omniglyph/-/omniglyph-1.4.0.tgz",
|
||||
"integrity": "sha512-4zAqDW9pBb2i+fiGOVLIKbdecZeo55UmKQoLku1apxo3TSA4gfqcMo2MqQpal1VicckUwTbexFLasW7qngXUHA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"gpt-tokenizer": "^3.4.0"
|
||||
|
||||
@@ -304,7 +304,7 @@
|
||||
"next-intl": "^4.13.6",
|
||||
"next-themes": "^0.4.6",
|
||||
"node-machine-id": "^1.1.12",
|
||||
"omniglyph": "^1.0.2",
|
||||
"omniglyph": "^1.4.0",
|
||||
"open": "^11.0.0",
|
||||
"ora": "^9.4.1",
|
||||
"parse5": "^8.0.1",
|
||||
|
||||
@@ -17,10 +17,22 @@ import { SAMPLE_BEFORE_TEXT, SAMPLE_PAGE_PNG_DATA_URI, SAMPLE_METRICS } from "./
|
||||
|
||||
interface CompressionConfigLite {
|
||||
engines?: Record<string, { enabled: boolean; level?: string }>;
|
||||
omniglyph?: { profile?: string };
|
||||
}
|
||||
|
||||
type EngineMap = Record<string, { enabled: boolean; level?: string }>;
|
||||
|
||||
/** Perfis do pacote, na ordem do mais permissivo ao mais restrito. O primeiro é
|
||||
* o default: a política que os recibos publicados mediram. */
|
||||
const PROFILES = [
|
||||
{ id: "aggressive", key: "aggressive" },
|
||||
{ id: "balanced", key: "balanced" },
|
||||
{ id: "coding-safe", key: "codingSafe" },
|
||||
{ id: "passthrough", key: "passthrough" },
|
||||
] as const;
|
||||
|
||||
type ProfileId = (typeof PROFILES)[number]["id"];
|
||||
|
||||
/** The measured fail-closed gate chain, in evaluation order. Every no-op is telemetered
|
||||
* as `skip:<reason>`; the engine only fires when all pass. */
|
||||
const GATES = [
|
||||
@@ -157,6 +169,40 @@ function GatesCard() {
|
||||
);
|
||||
}
|
||||
|
||||
function ProfileCard(props: {
|
||||
profile: ProfileId;
|
||||
disabled: boolean;
|
||||
onChange: (next: ProfileId) => void;
|
||||
}) {
|
||||
const t = useTranslations("omniglyph");
|
||||
const selected = PROFILES.find((p) => p.id === props.profile) ?? PROFILES[0];
|
||||
return (
|
||||
<Card className="p-6">
|
||||
<div className="flex flex-col gap-3">
|
||||
<h2 className="text-lg font-semibold">{t("profileTitle")}</h2>
|
||||
<p className="max-w-xl text-sm text-text-muted">{t("profileDescription")}</p>
|
||||
<select
|
||||
className="w-full max-w-sm rounded-md border border-border bg-surface px-3 py-2 text-sm"
|
||||
value={props.profile}
|
||||
disabled={props.disabled}
|
||||
aria-label={t("profileAria")}
|
||||
data-testid="omniglyph-profile-select"
|
||||
onChange={(e) => props.onChange(e.target.value as ProfileId)}
|
||||
>
|
||||
{PROFILES.map((p) => (
|
||||
<option key={p.id} value={p.id}>
|
||||
{t(`profiles.${p.key}.label`)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<span className="max-w-xl text-xs text-text-muted">
|
||||
{t(`profiles.${selected.key}.description`)}
|
||||
</span>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
function EnableCard(props: {
|
||||
enabled: boolean;
|
||||
disabled: boolean;
|
||||
@@ -199,6 +245,7 @@ function EnableCard(props: {
|
||||
export default function OmniglyphContextPageClient() {
|
||||
const [engines, setEngines] = useState<EngineMap>({});
|
||||
const [enabled, setEnabled] = useState(false);
|
||||
const [profile, setProfile] = useState<ProfileId>("aggressive");
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [status, setStatus] = useState<"" | "saved" | "error">("");
|
||||
@@ -210,6 +257,8 @@ export default function OmniglyphContextPageClient() {
|
||||
const e = data?.engines ?? {};
|
||||
setEngines(e);
|
||||
setEnabled(e.omniglyph?.enabled === true);
|
||||
const stored = data?.omniglyph?.profile;
|
||||
if (PROFILES.some((p) => p.id === stored)) setProfile(stored as ProfileId);
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => setLoading(false));
|
||||
@@ -245,12 +294,41 @@ export default function OmniglyphContextPageClient() {
|
||||
}
|
||||
};
|
||||
|
||||
// O perfil vive na config do engine (não no mapa `engines`), então é um PATCH
|
||||
// próprio — misturá-lo no payload do toggle reescreveria o mapa inteiro.
|
||||
const changeProfile = async (next: ProfileId) => {
|
||||
const previous = profile;
|
||||
setProfile(next);
|
||||
setSaving(true);
|
||||
setStatus("");
|
||||
try {
|
||||
const res = await fetch("/api/settings/compression", {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ omniglyph: { profile: next } }),
|
||||
});
|
||||
if (res.ok) {
|
||||
setStatus("saved");
|
||||
setTimeout(() => setStatus(""), 2000);
|
||||
} else {
|
||||
setProfile(previous);
|
||||
setStatus("error");
|
||||
}
|
||||
} catch {
|
||||
setProfile(previous);
|
||||
setStatus("error");
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mx-auto flex max-w-4xl flex-col gap-6 p-6" data-testid="omniglyph-page">
|
||||
<PageHeader />
|
||||
<EconomicsCard />
|
||||
<BeforeAfterCard />
|
||||
<GatesCard />
|
||||
<ProfileCard profile={profile} disabled={loading || saving} onChange={changeProfile} />
|
||||
<EnableCard
|
||||
enabled={enabled}
|
||||
disabled={loading || saving}
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
import { logger } from "@omniroute/open-sse/utils/logger.ts";
|
||||
import { estimateTokens } from "@omniroute/open-sse/services/contextManager.ts";
|
||||
import { adaptBodyForCompression } from "@omniroute/open-sse/services/compression/bodyAdapter.ts";
|
||||
import { resolveOmniGlyphTransport } from "@omniroute/open-sse/services/compression/imageTransportPolicy.ts";
|
||||
import type {
|
||||
CompressionConfig,
|
||||
CompressionResult,
|
||||
@@ -55,13 +56,16 @@ export async function applyResponsesWsCompression(
|
||||
if (!enabled || !settings) return responseBody;
|
||||
|
||||
const adapter = adaptBodyForCompression(responseBody);
|
||||
if (!adapter.adapted || !Array.isArray(adapter.body.messages) || adapter.body.messages.length === 0) {
|
||||
if (
|
||||
!adapter.adapted ||
|
||||
!Array.isArray(adapter.body.messages) ||
|
||||
adapter.body.messages.length === 0
|
||||
) {
|
||||
return responseBody;
|
||||
}
|
||||
|
||||
const { selectCompressionStrategy, applyCompressionAsync } = await import(
|
||||
"@omniroute/open-sse/services/compression/strategySelector.ts"
|
||||
);
|
||||
const { selectCompressionStrategy, applyCompressionAsync } =
|
||||
await import("@omniroute/open-sse/services/compression/strategySelector.ts");
|
||||
|
||||
const estimatedTokens = estimateTokens(adapter.body.messages);
|
||||
const cachingContext = {
|
||||
@@ -83,8 +87,8 @@ export async function applyResponsesWsCompression(
|
||||
|
||||
const result = await applyCompressionAsync(adapter.body, mode, {
|
||||
model: ctx.model,
|
||||
providerTransport:
|
||||
ctx.provider === "anthropic" || ctx.provider === "claude" ? "direct" : "aggregator",
|
||||
...resolveOmniGlyphTransport(ctx.provider),
|
||||
provider: ctx.provider,
|
||||
config: settings as CompressionConfig,
|
||||
cachingContext,
|
||||
});
|
||||
|
||||
@@ -8534,13 +8534,13 @@
|
||||
},
|
||||
"omniglyph": {
|
||||
"preview": "Preview",
|
||||
"description": "Context-as-image compression. Renders the system prompt, tool documentation, and dense history as compact PNG pages that Claude Fable 5 reads instead of text. Image tokens are billed by dimensions rather than characters, so the converted block costs about 10× less. Direct Anthropic route only.",
|
||||
"description": "Context-as-image compression. Renders the system prompt, tool documentation, and dense history as compact PNG pages for the measured Claude Fable 5 path. The published OmniGlyph package also exposes native GPT 5.6 transformers, but OmniRoute keeps those routes fail-closed until provider fidelity and reading receipts exist.",
|
||||
"economicsTitle": "The economics",
|
||||
"economics": {
|
||||
"fewerTokens": "fewer tokens on the converted block",
|
||||
"savings": "end-to-end savings (measured)",
|
||||
"imageTokens": "image tokens for a 1568×728 page (about 28k characters)",
|
||||
"accuracy": "reading accuracy on Fable 5 (n=30)"
|
||||
"accuracy": "scoped Fable 5 reading receipt"
|
||||
},
|
||||
"beforeAfterTitle": "Before → after",
|
||||
"blockSavings": "−{percent}% tokens on this block",
|
||||
@@ -8553,18 +8553,18 @@
|
||||
"gates": {
|
||||
"model": {
|
||||
"label": "Model",
|
||||
"pass": "claude-fable-5",
|
||||
"why": "Only Fable 5 reads dense pages at 100% accuracy (measured, n=30). GPT-5.5 and Gemini 2.5 Flash are blocked."
|
||||
"pass": "claude-fable-5 (measured); gpt-5.6 (HOLD)",
|
||||
"why": "Only the measured Fable 5 path is enabled in OmniRoute. GPT 5.6 is structurally supported by the package but remains blocked until its provider receipt; unverified families such as Grok stay text-only."
|
||||
},
|
||||
"transport": {
|
||||
"label": "Transport",
|
||||
"pass": "direct Anthropic",
|
||||
"why": "Aggregators resample images and destroy legibility — only the direct route is authoritative."
|
||||
"pass": "direct provider",
|
||||
"why": "Aggregators may resample images and destroy legibility — only a direct provider route is authoritative."
|
||||
},
|
||||
"format": {
|
||||
"label": "Format",
|
||||
"pass": "native Claude",
|
||||
"why": "The body must use Claude format and must never put a system role inside messages."
|
||||
"pass": "native Claude/OpenAI",
|
||||
"why": "The body must match the provider wire: Claude Messages, OpenAI Chat Completions, or native OpenAI Responses input[]."
|
||||
},
|
||||
"profitable": {
|
||||
"label": "Profitable",
|
||||
@@ -8573,7 +8573,28 @@
|
||||
}
|
||||
},
|
||||
"enableTitle": "Enable the engine",
|
||||
"enableDescription": "Runs last in the stack (after RTK/Caveman cleans the text, OmniGlyph converts the remainder to images) and also runs standalone through <code>omniglyph</code> mode. This is a preview and remains off by default until end-to-end validation is complete.",
|
||||
"enableDescription": "Runs last in the stack after text engines clean the request, using the native provider wire; OpenAI Chat/Responses requests are compressed after translation so their shape is preserved. It also runs standalone through <code>omniglyph</code> mode. This is a preview and remains off by default until end-to-end validation is complete.",
|
||||
"profileTitle": "Compression profile",
|
||||
"profileDescription": "The profile is a ceiling, not a floor: a stricter profile cannot be reopened by a per-step override. Measured on this codebase: with coding-safe or balanced, a session that has not accumulated history yet compresses nothing, because both raise the minimum-characters threshold and keep the system prompt, tool schemas and tool results native.",
|
||||
"profiles": {
|
||||
"aggressive": {
|
||||
"label": "Aggressive (default)",
|
||||
"description": "The policy the published receipts measured. Images the system prompt, tool documentation and dense history."
|
||||
},
|
||||
"balanced": {
|
||||
"label": "Balanced",
|
||||
"description": "Keeps live state native and protects the last 8 turns; only collapses older closed history."
|
||||
},
|
||||
"codingSafe": {
|
||||
"label": "Coding-safe",
|
||||
"description": "Keeps authority, tool schemas and live tool output native, protecting the last 12 turns. Compresses nothing until the session accumulates history."
|
||||
},
|
||||
"passthrough": {
|
||||
"label": "Passthrough",
|
||||
"description": "Routes without transforming. The engine is skipped and the request is forwarded untouched."
|
||||
}
|
||||
},
|
||||
"profileAria": "Select the OmniGlyph compression profile",
|
||||
"saved": "Saved.",
|
||||
"saveFailed": "Could not save.",
|
||||
"enableAria": "Enable the OmniGlyph engine",
|
||||
|
||||
@@ -8534,13 +8534,13 @@
|
||||
},
|
||||
"omniglyph": {
|
||||
"preview": "Visualização",
|
||||
"description": "Compressão de contexto como imagem. Renderiza o prompt de sistema, a documentação de ferramentas e o histórico denso como páginas PNG compactas que o Claude Fable 5 lê em vez de texto. Os tokens de imagem são cobrados por dimensões em vez de caracteres, então o bloco convertido custa cerca de 10× menos. Somente na rota direta da Anthropic.",
|
||||
"description": "Compressão de contexto como imagem. Renderiza o prompt de sistema, a documentação de ferramentas e o histórico denso como páginas PNG compactas para a rota medida do Claude Fable 5. O pacote OmniGlyph também expõe transformadores nativos para GPT 5.6, mas o OmniRoute mantém essas rotas fail-closed até existirem recibos de fidelidade do provedor e de leitura.",
|
||||
"economicsTitle": "A economia",
|
||||
"economics": {
|
||||
"fewerTokens": "menos tokens no bloco convertido",
|
||||
"savings": "economia de ponta a ponta (medida)",
|
||||
"imageTokens": "tokens de imagem para uma página de 1568×728 (cerca de 28 mil caracteres)",
|
||||
"accuracy": "precisão de leitura no Fable 5 (n=30)"
|
||||
"accuracy": "recibo de leitura do Fable 5 (escopo medido)"
|
||||
},
|
||||
"beforeAfterTitle": "Antes → depois",
|
||||
"blockSavings": "−{percent}% de tokens neste bloco",
|
||||
@@ -8553,18 +8553,18 @@
|
||||
"gates": {
|
||||
"model": {
|
||||
"label": "Modelo",
|
||||
"pass": "claude-fable-5",
|
||||
"why": "Somente o Fable 5 lê páginas densas com 100% de precisão (medido, n=30). GPT-5.5 e Gemini 2.5 Flash são bloqueados."
|
||||
"pass": "claude-fable-5 (medido); gpt-5.6 (HOLD)",
|
||||
"why": "Somente a rota medida do Fable 5 está habilitada no OmniRoute. O GPT 5.6 tem suporte estrutural no pacote, mas continua bloqueado até seu recibo do provedor; famílias não verificadas, como Grok, permanecem em texto."
|
||||
},
|
||||
"transport": {
|
||||
"label": "Transporte",
|
||||
"pass": "direto Anthropic",
|
||||
"why": "Agregadores reamostram imagens e destroem a legibilidade — apenas a rota direta é confiável."
|
||||
"pass": "provedor direto",
|
||||
"why": "Agregadores podem reamostrar imagens e destruir a legibilidade — apenas uma rota direta do provedor é confiável."
|
||||
},
|
||||
"format": {
|
||||
"label": "Formato",
|
||||
"pass": "Claude nativo",
|
||||
"why": "O corpo deve usar o formato Claude e nunca colocar um papel system dentro de messages."
|
||||
"pass": "Claude/OpenAI nativo",
|
||||
"why": "O corpo precisa corresponder ao wire do provedor: Claude Messages, OpenAI Chat Completions ou input[] nativo do OpenAI Responses."
|
||||
},
|
||||
"profitable": {
|
||||
"label": "Rentável",
|
||||
@@ -8573,7 +8573,28 @@
|
||||
}
|
||||
},
|
||||
"enableTitle": "Ativar a engine",
|
||||
"enableDescription": "Executa por último na pilha (depois que RTK/Caveman limpa o texto, o OmniGlyph converte o restante em imagens) e também roda de forma independente pelo modo <code>omniglyph</code>. Este é um preview e permanece desativado por padrão até que a validação de ponta a ponta seja concluída.",
|
||||
"enableDescription": "Executa por último na pilha, depois que os engines de texto limpam a solicitação, usando o wire nativo do provedor; solicitações OpenAI Chat/Responses são comprimidas depois da tradução para preservar seu formato. Também roda de forma independente pelo modo <code>omniglyph</code>. Este é um preview e permanece desativado por padrão até que a validação de ponta a ponta seja concluída.",
|
||||
"profileTitle": "Perfil de compressão",
|
||||
"profileDescription": "O perfil é um teto, não um piso: um perfil mais restrito não pode ser reaberto por um override de passo. Medido nesta base: com coding-safe ou balanced, uma sessão que ainda não acumulou histórico não comprime nada, porque os dois elevam o mínimo de caracteres e mantêm o system prompt, os schemas de tools e os tool results nativos.",
|
||||
"profiles": {
|
||||
"aggressive": {
|
||||
"label": "Agressivo (padrão)",
|
||||
"description": "A política que os recibos publicados mediram. Imageia o system prompt, a documentação de tools e o histórico denso."
|
||||
},
|
||||
"balanced": {
|
||||
"label": "Equilibrado",
|
||||
"description": "Mantém o estado vivo nativo e protege os últimos 8 turnos; só colapsa histórico antigo já fechado."
|
||||
},
|
||||
"codingSafe": {
|
||||
"label": "Seguro para código",
|
||||
"description": "Mantém autoridade, schemas de tools e saída de ferramenta nativos, protegendo os últimos 12 turnos. Não comprime nada até a sessão acumular histórico."
|
||||
},
|
||||
"passthrough": {
|
||||
"label": "Sem transformação",
|
||||
"description": "Só roteia. A engine é pulada e o request segue intacto."
|
||||
}
|
||||
},
|
||||
"profileAria": "Selecionar o perfil de compressão do OmniGlyph",
|
||||
"saved": "Salvo.",
|
||||
"saveFailed": "Não foi possível salvar.",
|
||||
"enableAria": "Ativar a engine OmniGlyph",
|
||||
|
||||
@@ -8574,6 +8574,27 @@
|
||||
},
|
||||
"enableTitle": "Bật bộ máy",
|
||||
"enableDescription": "Chạy cuối cùng trong ngăn xếp (sau khi RTK/Caveman làm sạch văn bản, OmniGlyph chuyển phần còn lại thành hình ảnh) và cũng có thể chạy độc lập qua chế độ <code>omniglyph</code>. Đây là bản xem trước và mặc định vẫn tắt cho đến khi hoàn tất kiểm thử đầu cuối.",
|
||||
"profileTitle": "Hồ sơ nén",
|
||||
"profileDescription": "Hồ sơ là giới hạn trên, không phải giới hạn dưới: một hồ sơ chặt chẽ hơn không thể bị mở lại bằng ghi đè ở từng bước. Đo trên chính mã nguồn này: với coding-safe hoặc balanced, một phiên chưa tích lũy lịch sử sẽ không nén gì cả, vì cả hai đều nâng ngưỡng số ký tự tối thiểu và giữ nguyên system prompt, lược đồ công cụ và kết quả công cụ.",
|
||||
"profiles": {
|
||||
"aggressive": {
|
||||
"label": "Tích cực (mặc định)",
|
||||
"description": "Chính sách mà các biên nhận đã công bố đo được. Kết xuất system prompt, tài liệu công cụ và lịch sử dày đặc thành ảnh."
|
||||
},
|
||||
"balanced": {
|
||||
"label": "Cân bằng",
|
||||
"description": "Giữ nguyên trạng thái đang hoạt động và bảo vệ 8 lượt gần nhất; chỉ gộp lịch sử cũ đã khép lại."
|
||||
},
|
||||
"codingSafe": {
|
||||
"label": "An toàn cho lập trình",
|
||||
"description": "Giữ nguyên thẩm quyền, lược đồ công cụ và đầu ra công cụ đang hoạt động, bảo vệ 12 lượt gần nhất. Không nén gì cho đến khi phiên tích lũy lịch sử."
|
||||
},
|
||||
"passthrough": {
|
||||
"label": "Không biến đổi",
|
||||
"description": "Chỉ định tuyến. Engine bị bỏ qua và yêu cầu được chuyển tiếp nguyên vẹn."
|
||||
}
|
||||
},
|
||||
"profileAria": "Chọn hồ sơ nén của OmniGlyph",
|
||||
"saved": "Đã lưu.",
|
||||
"saveFailed": "Không thể lưu.",
|
||||
"enableAria": "Bật bộ máy OmniGlyph",
|
||||
|
||||
@@ -26,6 +26,8 @@ import {
|
||||
DEFAULT_CODEX_RESPONSES_CONFIG,
|
||||
type CodexResponsesConfig,
|
||||
type ContextEditingConfig,
|
||||
DEFAULT_OMNIGLYPH_CONFIG,
|
||||
type OmniglyphConfig,
|
||||
type EngineToggle,
|
||||
type HeadroomConfig,
|
||||
type McpAccessibilityConfig,
|
||||
@@ -304,6 +306,19 @@ function normalizeLanguageConfig(value: unknown): CompressionLanguageConfig {
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeOmniglyphConfig(value: unknown): OmniglyphConfig {
|
||||
const record = toRecord(value);
|
||||
const profile = record.profile;
|
||||
// Um perfil desconhecido não pode virar "roda com a política padrão": cai para
|
||||
// o default explícito, e o adapter ainda falha fechado se algo passar por aqui.
|
||||
return {
|
||||
profile:
|
||||
profile === "coding-safe" || profile === "balanced" || profile === "passthrough"
|
||||
? profile
|
||||
: DEFAULT_OMNIGLYPH_CONFIG.profile,
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeContextEditingConfig(value: unknown): ContextEditingConfig {
|
||||
const record = toRecord(value);
|
||||
return {
|
||||
@@ -621,6 +636,7 @@ export async function getCompressionSettings(): Promise<CompressionConfig> {
|
||||
...buildDetailConfigDefaults(),
|
||||
contextBudget: normalizeContextBudgetConfig(undefined),
|
||||
contextEditing: { ...DEFAULT_CONTEXT_EDITING_CONFIG },
|
||||
omniglyph: { ...DEFAULT_OMNIGLYPH_CONFIG },
|
||||
liveZone: { enabled: false },
|
||||
engines: {},
|
||||
activeComboId: null,
|
||||
@@ -744,6 +760,9 @@ export async function getCompressionSettings(): Promise<CompressionConfig> {
|
||||
case "contextEditing":
|
||||
config.contextEditing = normalizeContextEditingConfig(parsed);
|
||||
break;
|
||||
case "omniglyph":
|
||||
config.omniglyph = normalizeOmniglyphConfig(parsed);
|
||||
break;
|
||||
case "liveZone":
|
||||
config.liveZone = { enabled: toRecord(parsed).enabled === true };
|
||||
break;
|
||||
|
||||
@@ -346,6 +346,19 @@ export const contextBudgetConfigSchema = z
|
||||
})
|
||||
.strict();
|
||||
|
||||
/**
|
||||
* Perfil semântico do OmniGlyph. O perfil é um TETO: o pacote não deixa um
|
||||
* override reabrir uma lane que o perfil fechou. `aggressive` é o default e a
|
||||
* política que os recibos publicados mediram; `coding-safe`/`balanced` mantêm
|
||||
* system, schemas de tools e tool results nativos, e não comprimem nada até a
|
||||
* sessão acumular histórico.
|
||||
*/
|
||||
export const omniglyphConfigSchema = z
|
||||
.object({
|
||||
profile: z.enum(["coding-safe", "balanced", "aggressive", "passthrough"]),
|
||||
})
|
||||
.strict();
|
||||
|
||||
export const compressionSettingsUpdateSchema = z
|
||||
.object({
|
||||
enabled: z.boolean().optional(),
|
||||
@@ -373,6 +386,7 @@ export const compressionSettingsUpdateSchema = z
|
||||
ccr: ccrConfigSchema.optional(),
|
||||
contextBudget: contextBudgetConfigSchema.optional(),
|
||||
contextEditing: contextEditingConfigSchema.optional(),
|
||||
omniglyph: omniglyphConfigSchema.optional(),
|
||||
liveZone: z.object({ enabled: z.boolean() }).strict().optional(),
|
||||
engines: z.record(z.string(), engineToggleSchema).optional(),
|
||||
enginesExplicit: z.boolean().optional(),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert";
|
||||
import { estimateCompressionTokens } from "../../../open-sse/services/compression/stats.ts";
|
||||
import { transformAnthropicMessages } from "omniglyph";
|
||||
import { transformAnthropicMessages, transformOpenAIChatCompletions } from "omniglyph";
|
||||
|
||||
const CHARS_PER_TOKEN = 4;
|
||||
|
||||
@@ -67,3 +67,33 @@ test("regressão: corpo sem imagem estima o MESMO valor de antes (char-count pur
|
||||
const expected = Math.ceil(JSON.stringify(plainBody).length / CHARS_PER_TOKEN);
|
||||
assert.equal(estimateCompressionTokens(plainBody), expected);
|
||||
});
|
||||
|
||||
test("estimateCompressionTokens contabiliza image_url PNG no wire OpenAI", async () => {
|
||||
const originalBody = {
|
||||
model: "gpt-5.6",
|
||||
messages: [
|
||||
{ role: "system", content: DENSE },
|
||||
{ role: "user", content: "oi" },
|
||||
],
|
||||
};
|
||||
const encoded = new TextEncoder().encode(JSON.stringify(originalBody));
|
||||
const result = await transformOpenAIChatCompletions(encoded);
|
||||
assert.equal(result.info.compressed, true);
|
||||
const compressedBody = JSON.parse(new TextDecoder().decode(result.body)) as Record<
|
||||
string,
|
||||
unknown
|
||||
>;
|
||||
assert.ok(JSON.stringify(compressedBody).includes('"type":"image_url"'));
|
||||
|
||||
const originalEstimate = estimateCompressionTokens(originalBody);
|
||||
const compressedEstimate = estimateCompressionTokens(compressedBody);
|
||||
const naiveEstimate = Math.ceil(JSON.stringify(compressedBody).length / CHARS_PER_TOKEN);
|
||||
assert.ok(
|
||||
compressedEstimate < naiveEstimate,
|
||||
`expected image-aware (${compressedEstimate}) < base64 char estimate (${naiveEstimate})`
|
||||
);
|
||||
assert.ok(
|
||||
compressedEstimate < originalEstimate,
|
||||
`expected OpenAI image compression (${compressedEstimate}) < original (${originalEstimate})`
|
||||
);
|
||||
});
|
||||
|
||||
@@ -18,11 +18,17 @@ function claudeBody(): Record<string, unknown> {
|
||||
messages: [{ role: "user", content: [{ type: "text", text: "oi" }] }],
|
||||
};
|
||||
}
|
||||
const OK = { model: "claude-fable-5", supportsVision: true, providerTransport: "direct" as const };
|
||||
const OK = {
|
||||
model: "claude-fable-5",
|
||||
supportsVision: true,
|
||||
providerTransport: "direct" as const,
|
||||
imageTransportFidelity: "byte-preserving" as const,
|
||||
};
|
||||
const GPT_OK = {
|
||||
model: "gpt-5.6",
|
||||
supportsVision: true,
|
||||
providerTransport: "direct" as const,
|
||||
imageTransportFidelity: "byte-preserving" as const,
|
||||
sourceFormat: "openai" as const,
|
||||
targetFormat: "openai" as const,
|
||||
compressionStage: "post-translation" as const,
|
||||
@@ -82,6 +88,15 @@ test("skip fail-closed: sem supportsVision / transporte agregador / undefined",
|
||||
}
|
||||
});
|
||||
|
||||
test("skip fail-closed: rota direta sem recibo de fidelidade de imagem", async () => {
|
||||
const r = await omniglyphEngine.applyAsync!(claudeBody(), {
|
||||
...OK,
|
||||
imageTransportFidelity: "unknown",
|
||||
});
|
||||
assert.equal(r.compressed, false);
|
||||
assert.ok(r.stats?.techniquesUsed.includes("skip:transport_fidelity_unknown"));
|
||||
});
|
||||
|
||||
test("skip: modelo fora da allowlist medida", async () => {
|
||||
const body = { ...claudeBody(), model: "gpt-5.5" };
|
||||
const r = await omniglyphEngine.applyAsync!(body, { ...OK, model: "gpt-5.5" });
|
||||
@@ -110,6 +125,31 @@ test("cache_control do cliente sobrevive byte a byte", async () => {
|
||||
assert.ok(JSON.stringify(r.body).includes('"cache_control"'));
|
||||
});
|
||||
|
||||
test("preserveSystemPrompt mantém o sistema nativo e ainda pode comprimir tool_result", async () => {
|
||||
const body = {
|
||||
...claudeBody(),
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "tool_result",
|
||||
tool_use_id: "call_1",
|
||||
content: DENSE,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
const r = await omniglyphEngine.applyAsync!(body, {
|
||||
...OK,
|
||||
config: { preserveSystemPrompt: true } as never,
|
||||
});
|
||||
assert.equal(r.compressed, true);
|
||||
assert.equal((r.body as { system?: unknown }).system, DENSE);
|
||||
assert.ok(JSON.stringify(r.body).includes('"type":"image"'));
|
||||
});
|
||||
|
||||
test("apply síncrono é pass-through seguro (engine async-only)", () => {
|
||||
const body = claudeBody();
|
||||
const r = omniglyphEngine.apply(body, OK);
|
||||
@@ -147,3 +187,139 @@ test("OpenAI não roda no estágio pré-tradução", async () => {
|
||||
assert.equal(r.compressed, false);
|
||||
assert.ok(r.stats?.techniquesUsed.includes("skip:requires_post_translation"));
|
||||
});
|
||||
|
||||
// OmniGlyph 1.4.0 introduziu escopos de segurança (`coding-safe`/`balanced`/
|
||||
// `aggressive`/`passthrough`) e passou a resolvê-los, dentro de
|
||||
// `isOmniGlyphSupportedModel()`, lendo `process.env.OMNIGLYPH_PROFILE`. Isso
|
||||
// transforma uma variável de ambiente do HOST num gate silencioso de TODO
|
||||
// request do OmniRoute: um `OMNIGLYPH_PROFILE=passthrough` exportado no shell
|
||||
// do processo desligaria a engine sem que nenhuma configuração do OmniRoute
|
||||
// tivesse mudado — e sem nenhum sinal na UI. A política é do OmniRoute; o
|
||||
// adapter tem de passar o escopo explicitamente.
|
||||
async function withEnv<T>(key: string, value: string, fn: () => Promise<T>): Promise<T> {
|
||||
const had = Object.prototype.hasOwnProperty.call(process.env, key);
|
||||
const previous = process.env[key];
|
||||
process.env[key] = value;
|
||||
try {
|
||||
return await fn();
|
||||
} finally {
|
||||
if (had) process.env[key] = previous;
|
||||
else delete process.env[key];
|
||||
}
|
||||
}
|
||||
|
||||
test("OMNIGLYPH_PROFILE do host não decide o gate de modelo do OmniRoute", async () => {
|
||||
const r = await withEnv("OMNIGLYPH_PROFILE", "passthrough", () =>
|
||||
omniglyphEngine.applyAsync!(claudeBody(), OK)
|
||||
);
|
||||
assert.equal(
|
||||
r.compressed,
|
||||
true,
|
||||
"env do processo não pode desligar a engine: o escopo vem da config do OmniRoute"
|
||||
);
|
||||
});
|
||||
|
||||
test("OMNIGLYPH_PROFILE do host não amplia a allowlist de modelos do OmniRoute", async () => {
|
||||
const body = { ...claudeBody(), model: "claude-sonnet-5" };
|
||||
const r = await withEnv("OMNIGLYPH_PROFILE", "aggressive", () =>
|
||||
withEnv("OMNIGLYPH_MODELS", "claude-fable-5,claude-sonnet-5", () =>
|
||||
omniglyphEngine.applyAsync!(body, { ...OK, model: "claude-sonnet-5" })
|
||||
)
|
||||
);
|
||||
assert.equal(r.compressed, false, "modelo sem recibo medido não pode entrar via env do host");
|
||||
assert.ok(r.stats?.techniquesUsed.includes("skip:model_not_approved"));
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Perfis semânticos (OmniGlyph 1.4.0)
|
||||
//
|
||||
// `transformAnthropicMessages()` resolve e mescla o perfil sozinho, mas os
|
||||
// transformadores OpenAI recebem `TransformOptions` cru e NÃO conhecem
|
||||
// `profile`. Sem o merge explícito do host, um perfil escolhido pelo operador
|
||||
// valeria no wire Claude e seria silenciosamente ignorado no wire OpenAI.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** Histórico longo o bastante para o colapso valer mesmo com o system nativo. */
|
||||
function claudeBodyWithHistory(): Record<string, unknown> {
|
||||
const messages: Array<Record<string, unknown>> = [];
|
||||
for (let i = 0; i < 40; i++) {
|
||||
messages.push({ role: "user", content: [{ type: "text", text: `pergunta ${i}: ${DENSE}` }] });
|
||||
messages.push({
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: `resposta ${i}: ${DENSE}` }],
|
||||
});
|
||||
}
|
||||
messages.push({ role: "user", content: [{ type: "text", text: "oi" }] });
|
||||
return { model: "claude-fable-5", max_tokens: 128, system: DENSE, messages };
|
||||
}
|
||||
|
||||
test("perfil coding-safe chega ao wire OpenAI (não só ao wrapper Anthropic)", async () => {
|
||||
const aggressive = await omniglyphEngine.applyAsync!(openaiChatBody(), {
|
||||
...GPT_OK,
|
||||
stepConfig: { profile: "aggressive" } as never,
|
||||
});
|
||||
assert.equal(aggressive.compressed, true, "aggressive é a política medida atual");
|
||||
|
||||
const codingSafe = await omniglyphEngine.applyAsync!(openaiChatBody(), {
|
||||
...GPT_OK,
|
||||
stepConfig: { profile: "coding-safe" } as never,
|
||||
});
|
||||
assert.equal(
|
||||
codingSafe.compressed,
|
||||
false,
|
||||
"coding-safe mantém system e schemas de tools nativos no wire OpenAI"
|
||||
);
|
||||
assert.ok(
|
||||
codingSafe.stats?.techniquesUsed.some((t) => t.startsWith("skip:below_min_chars")),
|
||||
`esperado skip por minCompressChars, veio ${JSON.stringify(codingSafe.stats?.techniquesUsed)}`
|
||||
);
|
||||
});
|
||||
|
||||
test("perfil é TETO, não piso: coding-safe mantém o system nativo mesmo sem preserveSystemPrompt", async () => {
|
||||
const body = claudeBodyWithHistory();
|
||||
const r = await omniglyphEngine.applyAsync!(body, {
|
||||
...OK,
|
||||
stepConfig: { profile: "coding-safe", preserveSystemPrompt: false } as never,
|
||||
});
|
||||
assert.equal(r.compressed, true, "o histórico antigo ainda colapsa");
|
||||
assert.equal(
|
||||
(r.body as { system?: unknown }).system,
|
||||
DENSE,
|
||||
"coding-safe não deixa um override do chamador reabrir a compressão do system"
|
||||
);
|
||||
});
|
||||
|
||||
test("perfil passthrough desliga a engine sem tocar no corpo", async () => {
|
||||
const body = claudeBodyWithHistory();
|
||||
const r = await omniglyphEngine.applyAsync!(body, {
|
||||
...OK,
|
||||
stepConfig: { profile: "passthrough" } as never,
|
||||
});
|
||||
assert.equal(r.compressed, false);
|
||||
assert.ok(r.stats?.techniquesUsed.includes("skip:profile_passthrough"));
|
||||
assert.deepEqual(r.body, body);
|
||||
});
|
||||
|
||||
test("perfil inválido falha fechado, não propaga exceção", async () => {
|
||||
const r = await omniglyphEngine.applyAsync!(claudeBody(), {
|
||||
...OK,
|
||||
stepConfig: { profile: "turbo-max" } as never,
|
||||
});
|
||||
assert.equal(r.compressed, false);
|
||||
assert.ok(r.stats?.techniquesUsed.includes("skip:invalid_profile"));
|
||||
});
|
||||
|
||||
test("preserveSystemPrompt: wire OpenAI pula, porque o pacote não sabe preservar system lá", async () => {
|
||||
// O transform OpenAI do OmniGlyph 1.4.0 honra apenas compressTools, gptHistory,
|
||||
// minCompressChars e reflow — não existe compressSystem nesse wire, e a
|
||||
// instrução vira sempre um ponteiro para a imagem. Imagear assim queimaria o
|
||||
// prefixo quente que a decisão cache-aware do OmniRoute mandou preservar, e o
|
||||
// OmniRoute não teria como saber. Sem opção de honrar a política, pula.
|
||||
const r = await omniglyphEngine.applyAsync!(openaiChatBody(), {
|
||||
...GPT_OK,
|
||||
config: { preserveSystemPrompt: true } as never,
|
||||
});
|
||||
assert.equal(r.compressed, false);
|
||||
assert.ok(r.stats?.techniquesUsed.includes("skip:system_preservation_unsupported_on_wire"));
|
||||
assert.deepEqual(r.body, openaiChatBody());
|
||||
});
|
||||
|
||||
@@ -1,12 +1,27 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import assert from "node:assert/strict";
|
||||
import { test } from "node:test";
|
||||
import { resolveOmniGlyphTransport } from "../../../open-sse/services/compression/imageTransportPolicy.ts";
|
||||
|
||||
test("chatCore treats both Anthropic providers as direct OmniGlyph transports", () => {
|
||||
test("chatCore uses the measured Anthropic byte-preserving transport policy", () => {
|
||||
const chatCore = readFileSync("open-sse/handlers/chatCore.ts", "utf8");
|
||||
|
||||
assert.match(
|
||||
chatCore,
|
||||
/providerTransport:\s*provider === "anthropic" \|\| provider === "claude"[\s\S]{0,80}?"direct"/
|
||||
);
|
||||
assert.match(chatCore, /resolveOmniGlyphTransport\(provider\)/);
|
||||
const translationIndex = chatCore.indexOf("translatedBody = translateRequest(");
|
||||
const postCompressionIndex = chatCore.indexOf("if (runPostTranslationCompression");
|
||||
assert.ok(translationIndex >= 0, "chatCore deve manter a tradução explícita");
|
||||
assert.ok(postCompressionIndex > translationIndex, "OmniGlyph target-wire roda após tradução");
|
||||
assert.match(chatCore, /compressionStage: "post-translation"/);
|
||||
assert.deepEqual(resolveOmniGlyphTransport("anthropic"), {
|
||||
providerTransport: "direct",
|
||||
imageTransportFidelity: "byte-preserving",
|
||||
});
|
||||
assert.deepEqual(resolveOmniGlyphTransport("claude"), {
|
||||
providerTransport: "direct",
|
||||
imageTransportFidelity: "byte-preserving",
|
||||
});
|
||||
assert.deepEqual(resolveOmniGlyphTransport("openai"), {
|
||||
providerTransport: "aggregator",
|
||||
imageTransportFidelity: "unknown",
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,6 +6,7 @@ test("pacote omniglyph exporta a API que o adapter consome", async () => {
|
||||
assert.equal(typeof mod.transformAnthropicMessages, "function");
|
||||
assert.equal(typeof mod.transformOpenAIChatCompletions, "function");
|
||||
assert.equal(typeof mod.transformOpenAIResponses, "function");
|
||||
assert.equal(typeof mod.transformRequest, "function");
|
||||
assert.equal(typeof mod.isOmniGlyphSupportedModel, "function");
|
||||
assert.equal(typeof mod.isOmniGlyphSupportedGptModel, "function");
|
||||
assert.equal(mod.isOmniGlyphSupportedModel("claude-fable-5"), true);
|
||||
@@ -14,3 +15,30 @@ test("pacote omniglyph exporta a API que o adapter consome", async () => {
|
||||
const applicability = await import("omniglyph/applicability");
|
||||
assert.equal(typeof applicability.isModelImageable, "function");
|
||||
});
|
||||
|
||||
// Superfícies introduzidas no 1.4.0. Sem esta asserção, uma remoção upstream só
|
||||
// apareceria em runtime — o adapter importa esses símbolos diretamente.
|
||||
test("omniglyph 1.4.0 exporta escopo de segurança, perfis e accounting", async () => {
|
||||
const mod = await import("omniglyph");
|
||||
|
||||
// Gate de modelo por escopo explícito (não pela env do processo).
|
||||
assert.equal(typeof mod.isOmniGlyphSupportedModelForScope, "function");
|
||||
assert.equal(mod.isOmniGlyphSupportedModelForScope("claude-fable-5", "coding-safe"), true);
|
||||
assert.equal(mod.isOmniGlyphSupportedModelForScope("claude-sonnet-5", "coding-safe"), false);
|
||||
assert.equal(
|
||||
mod.isOmniGlyphSupportedModelForScope("claude-fable-5", "passthrough"),
|
||||
false,
|
||||
"passthrough não habilita modelo nenhum"
|
||||
);
|
||||
|
||||
// Perfis semânticos.
|
||||
assert.equal(typeof mod.resolveCompressionProfile, "function");
|
||||
assert.equal(typeof mod.mergeCompressionProfileOptions, "function");
|
||||
assert.equal(typeof mod.shouldKeepToolResultSharp, "function");
|
||||
assert.equal(mod.resolveCompressionProfile("coding-safe").name, "coding-safe");
|
||||
assert.throws(() => mod.resolveCompressionProfile("nao-existe"));
|
||||
|
||||
// Contabilidade física normalizada.
|
||||
assert.equal(typeof mod.normalizeAccounting, "function");
|
||||
assert.equal(typeof mod.providerActualInputTokens, "function");
|
||||
});
|
||||
|
||||
@@ -18,6 +18,20 @@ const body = () => ({
|
||||
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
|
||||
@@ -30,6 +44,7 @@ test("stacked com step omniglyph recebe providerTransport (engine roda, não é
|
||||
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");
|
||||
@@ -59,3 +74,122 @@ test("stacked sem providerTransport 'direct' pula omniglyph (transport_not_direc
|
||||
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");
|
||||
});
|
||||
|
||||
83
tests/unit/compression/omniglyph-profile-config.test.ts
Normal file
83
tests/unit/compression/omniglyph-profile-config.test.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* Perfil semântico do OmniGlyph (pacote 1.4.0) — persistência ponta a ponta.
|
||||
*
|
||||
* Cobre:
|
||||
* 1. compressionSettingsUpdateSchema aceita omniglyph:{profile} e RECUSA nome inválido
|
||||
* 2. updateCompressionSettings / getCompressionSettings fazem round-trip do perfil
|
||||
* 3. o default persistido é `aggressive` (a política que os recibos mediram)
|
||||
* 4. um perfil desconhecido vindo do storage cai para o default, não vaza adiante
|
||||
*/
|
||||
import { describe, it, beforeEach, afterEach, after } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
|
||||
import { compressionSettingsUpdateSchema } from "../../../src/shared/validation/compressionConfigSchemas.ts";
|
||||
import { DEFAULT_OMNIGLYPH_CONFIG } from "../../../open-sse/services/compression/types.ts";
|
||||
|
||||
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-omniglyph-profile-"));
|
||||
const ORIGINAL_DATA_DIR = process.env.DATA_DIR;
|
||||
process.env.DATA_DIR = TEST_DATA_DIR;
|
||||
|
||||
const core = await import("../../../src/lib/db/core.ts");
|
||||
const { getCompressionSettings, updateCompressionSettings } = await import(
|
||||
"../../../src/lib/db/compression.ts"
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
core.resetDbInstance();
|
||||
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
|
||||
fs.mkdirSync(TEST_DATA_DIR, { recursive: true });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
core.resetDbInstance();
|
||||
});
|
||||
|
||||
after(() => {
|
||||
core.resetDbInstance();
|
||||
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
|
||||
if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR;
|
||||
else process.env.DATA_DIR = ORIGINAL_DATA_DIR;
|
||||
});
|
||||
|
||||
describe("omniglyph profile config", () => {
|
||||
it("o schema aceita os quatro perfis e recusa qualquer outro nome", () => {
|
||||
for (const profile of ["coding-safe", "balanced", "aggressive", "passthrough"]) {
|
||||
const parsed = compressionSettingsUpdateSchema.safeParse({ omniglyph: { profile } });
|
||||
assert.equal(parsed.success, true, `perfil válido recusado: ${profile}`);
|
||||
}
|
||||
// Um nome inválido tem de morrer no schema. Se passar, o adapter ainda falha
|
||||
// fechado (skip:invalid_profile), mas o operador veria a engine desligada
|
||||
// sem nenhum erro na tela de configuração.
|
||||
assert.equal(
|
||||
compressionSettingsUpdateSchema.safeParse({ omniglyph: { profile: "turbo-max" } }).success,
|
||||
false
|
||||
);
|
||||
assert.equal(
|
||||
compressionSettingsUpdateSchema.safeParse({ omniglyph: { profile: "" } }).success,
|
||||
false
|
||||
);
|
||||
});
|
||||
|
||||
it("o default é aggressive — a política que os recibos publicados mediram", async () => {
|
||||
assert.equal(DEFAULT_OMNIGLYPH_CONFIG.profile, "aggressive");
|
||||
assert.equal((await getCompressionSettings()).omniglyph?.profile, "aggressive");
|
||||
});
|
||||
|
||||
it("round-trip do perfil escolhido pelo operador", async () => {
|
||||
await updateCompressionSettings({ omniglyph: { profile: "coding-safe" } });
|
||||
assert.equal((await getCompressionSettings()).omniglyph?.profile, "coding-safe");
|
||||
|
||||
await updateCompressionSettings({ omniglyph: { profile: "passthrough" } });
|
||||
assert.equal((await getCompressionSettings()).omniglyph?.profile, "passthrough");
|
||||
});
|
||||
|
||||
it("perfil desconhecido no storage cai para o default em vez de vazar adiante", async () => {
|
||||
await updateCompressionSettings({ omniglyph: { profile: "coding-safe" } });
|
||||
// Simula uma linha gravada por uma versão futura/adulterada, contornando o schema.
|
||||
await updateCompressionSettings({ omniglyph: { profile: "turbo-max" } } as never);
|
||||
assert.equal((await getCompressionSettings()).omniglyph?.profile, "aggressive");
|
||||
});
|
||||
});
|
||||
@@ -3,8 +3,13 @@ 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 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,
|
||||
@@ -18,6 +23,7 @@ test("modo omniglyph sozinho comprime (selecionar o modo é o enable)", async ()
|
||||
model: "claude-fable-5",
|
||||
supportsVision: true,
|
||||
providerTransport: "direct",
|
||||
imageTransportFidelity: "byte-preserving",
|
||||
});
|
||||
assert.equal(r.compressed, true);
|
||||
assert.ok(JSON.stringify(r.body).includes('"type":"image"'));
|
||||
|
||||
121
tests/unit/compression/omniglyph-telemetry.test.ts
Normal file
121
tests/unit/compression/omniglyph-telemetry.test.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert";
|
||||
import { omniglyphEngine } from "../../../open-sse/services/compression/engines/omniglyphAdapter.ts";
|
||||
|
||||
// O OmniGlyph 1.4.0 expõe `normalizeAccounting()`, que classifica o GRAU DE
|
||||
// EVIDÊNCIA da economia (provider-reported / estimated / bytes-only /
|
||||
// unavailable) e resolve a semântica de cache por provider — Anthropic usa
|
||||
// buckets disjuntos, OpenAI/xAI reportam cached como subconjunto do input.
|
||||
// Somar à mão dá double-count. Antes disso o adapter descartava tudo e a UI
|
||||
// exibia um número sem dizer de onde ele veio.
|
||||
|
||||
const SEGREDO = "sk-ant-api03-SEGREDO-QUE-NAO-PODE-VAZAR-NA-TELEMETRIA";
|
||||
const DENSE =
|
||||
"X".repeat(500) +
|
||||
"\n" +
|
||||
Array.from(
|
||||
{ length: 400 },
|
||||
(_, i) => `const row_${i} = compute(${i * 17}, "${"v".repeat(80)}");`
|
||||
).join("\n");
|
||||
|
||||
function claudeBody(): Record<string, unknown> {
|
||||
return {
|
||||
model: "claude-fable-5",
|
||||
max_tokens: 128,
|
||||
system: `${DENSE}\nAPI_KEY=${SEGREDO}\ncwd=/home/operador/projeto-secreto`,
|
||||
messages: [{ role: "user", content: [{ type: "text", text: "oi" }] }],
|
||||
};
|
||||
}
|
||||
|
||||
const OK = {
|
||||
model: "claude-fable-5",
|
||||
provider: "anthropic",
|
||||
supportsVision: true,
|
||||
providerTransport: "direct" as const,
|
||||
imageTransportFidelity: "byte-preserving" as const,
|
||||
};
|
||||
|
||||
test("stats do omniglyph carregam a contabilidade normalizada com grau de evidência", async () => {
|
||||
const r = await omniglyphEngine.applyAsync!(claudeBody(), OK as never);
|
||||
assert.equal(r.compressed, true);
|
||||
|
||||
const accounting = r.stats?.omniglyph;
|
||||
assert.ok(accounting, "compressão aplicada deve registrar a contabilidade");
|
||||
assert.equal(accounting.provider, "anthropic");
|
||||
assert.ok(
|
||||
["provider-reported", "estimated", "bytes-only", "unavailable"].includes(
|
||||
accounting.savings.evidence
|
||||
),
|
||||
`evidência inesperada: ${accounting.savings.evidence}`
|
||||
);
|
||||
// Sem contadores do provider nesta etapa, a base honesta é o tamanho do corpo.
|
||||
assert.equal(accounting.savings.evidence, "bytes-only");
|
||||
assert.ok(typeof accounting.bytes.original === "number" && accounting.bytes.original > 0);
|
||||
assert.ok(typeof accounting.bytes.transformed === "number");
|
||||
assert.ok(typeof accounting.images.count === "number" && accounting.images.count > 0);
|
||||
assert.ok(typeof accounting.images.bytes === "number" && accounting.images.bytes > 0);
|
||||
});
|
||||
|
||||
test("telemetria não carrega conteúdo: nem base64, nem prompt, nem segredo, nem ambiente", async () => {
|
||||
const r = await omniglyphEngine.applyAsync!(claudeBody(), OK as never);
|
||||
assert.equal(r.compressed, true);
|
||||
|
||||
const serialized = JSON.stringify(r.stats);
|
||||
|
||||
// Valores do request.
|
||||
assert.ok(!serialized.includes(SEGREDO), "segredo do system vazou na telemetria");
|
||||
assert.ok(!serialized.includes("projeto-secreto"), "caminho do operador vazou");
|
||||
assert.ok(!serialized.includes("const row_0 = compute"), "texto do system vazou");
|
||||
assert.ok(!serialized.includes("data:image/png;base64"), "data URL de imagem vazou");
|
||||
assert.ok(!serialized.includes("iVBORw0KGgo"), "cabeçalho PNG em base64 vazou");
|
||||
|
||||
// Chaves do TransformInfo que carregam conteúdo, hash de conteúdo ou ambiente.
|
||||
for (const proibida of [
|
||||
"imageSourceText",
|
||||
"imageSourceTexts",
|
||||
"recoverable",
|
||||
"systemSha8",
|
||||
"claudeMdSha8",
|
||||
"firstUserSha8",
|
||||
"unknownStaticTags",
|
||||
"churningStaticTags",
|
||||
'"env"',
|
||||
]) {
|
||||
assert.ok(!serialized.includes(proibida), `campo proibido na telemetria: ${proibida}`);
|
||||
}
|
||||
|
||||
// A allowlist é positiva: todo valor da contabilidade é número, string de enum
|
||||
// conhecida, ou objeto desses. Nada de texto livre vindo do request.
|
||||
const accounting = r.stats?.omniglyph;
|
||||
assert.ok(accounting);
|
||||
const enums = new Set([
|
||||
"anthropic",
|
||||
"openai",
|
||||
"xai",
|
||||
"unknown",
|
||||
"provider-reported",
|
||||
"estimated",
|
||||
"bytes-only",
|
||||
"unavailable",
|
||||
"claude-fable-5",
|
||||
]);
|
||||
const walk = (value: unknown, path: string): void => {
|
||||
if (value === undefined || value === null || typeof value === "number") return;
|
||||
if (typeof value === "string") {
|
||||
assert.ok(enums.has(value), `string fora da allowlist em ${path}: ${value}`);
|
||||
return;
|
||||
}
|
||||
assert.equal(typeof value, "object", `tipo inesperado em ${path}`);
|
||||
for (const [k, v] of Object.entries(value as Record<string, unknown>)) walk(v, `${path}.${k}`);
|
||||
};
|
||||
walk(accounting, "omniglyph");
|
||||
});
|
||||
|
||||
test("skip não inventa contabilidade", async () => {
|
||||
const r = await omniglyphEngine.applyAsync!(claudeBody(), {
|
||||
...OK,
|
||||
imageTransportFidelity: "unknown",
|
||||
} as never);
|
||||
assert.equal(r.compressed, false);
|
||||
assert.equal(r.stats?.omniglyph, undefined);
|
||||
});
|
||||
@@ -108,7 +108,11 @@ describe("OmniglyphContextPage", () => {
|
||||
expect(img!.getAttribute("src")).toMatch(/^data:image\/png;base64,/);
|
||||
// Gates
|
||||
expect(text).toContain("claude-fable-5");
|
||||
expect(text).toContain("direct Anthropic");
|
||||
// A cópia do gate deixou de dizer "direct Anthropic" quando os wires OpenAI
|
||||
// nativos entraram: transporte direto é condição de TODO provider, e o que
|
||||
// restringe a rota ao Anthropic é o recibo de fidelidade de bytes, não o
|
||||
// rótulo do transporte.
|
||||
expect(text).toContain("direct provider");
|
||||
// Config control
|
||||
expect(container.querySelector('[data-testid="omniglyph-enable-toggle"]')).toBeTruthy();
|
||||
});
|
||||
@@ -138,4 +142,66 @@ describe("OmniglyphContextPage", () => {
|
||||
expect(engines.rtk?.enabled).toBe(true);
|
||||
expect(engines.caveman?.enabled).toBe(false);
|
||||
});
|
||||
|
||||
it("carrega o perfil salvo e faz PATCH só do perfil, sem reescrever o mapa de engines", async () => {
|
||||
const puts: CapturedPut[] = [];
|
||||
const json = (body: unknown, status = 200) =>
|
||||
new Response(JSON.stringify(body), {
|
||||
status,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
const stored = {
|
||||
enabled: true,
|
||||
engines: { rtk: { enabled: true, level: "standard" } },
|
||||
omniglyph: { profile: "coding-safe" },
|
||||
};
|
||||
vi.spyOn(globalThis, "fetch").mockImplementation(
|
||||
async (input: RequestInfo | URL, init?: RequestInit) => {
|
||||
const url = input.toString();
|
||||
const method = (init?.method ?? "GET").toUpperCase();
|
||||
if (url.includes("/api/settings/compression")) {
|
||||
if (method === "PUT") {
|
||||
puts.push({ url, body: JSON.parse(String(init?.body ?? "{}")) });
|
||||
return json(stored);
|
||||
}
|
||||
return json(stored);
|
||||
}
|
||||
return json({}, 404);
|
||||
}
|
||||
);
|
||||
|
||||
const { default: Page } = await import(
|
||||
"../../../src/app/(dashboard)/dashboard/context/omniglyph/OmniglyphContextPageClient"
|
||||
);
|
||||
let container!: HTMLElement;
|
||||
await act(async () => {
|
||||
container = mount(<Page />);
|
||||
});
|
||||
await flush();
|
||||
|
||||
const select = container.querySelector(
|
||||
'[data-testid="omniglyph-profile-select"]'
|
||||
) as HTMLSelectElement | null;
|
||||
expect(select, "o seletor de perfil deve existir").toBeTruthy();
|
||||
expect(select!.value, "o perfil salvo tem de vir selecionado").toBe("coding-safe");
|
||||
|
||||
// Os quatro perfis do pacote, com aggressive como primeiro (default).
|
||||
expect(Array.from(select!.options).map((o) => o.value)).toEqual([
|
||||
"aggressive",
|
||||
"balanced",
|
||||
"coding-safe",
|
||||
"passthrough",
|
||||
]);
|
||||
|
||||
await act(async () => {
|
||||
select!.value = "passthrough";
|
||||
select!.dispatchEvent(new Event("change", { bubbles: true }));
|
||||
});
|
||||
await flush();
|
||||
|
||||
expect(puts.length).toBe(1);
|
||||
expect(puts[0]!.body).toEqual({ omniglyph: { profile: "passthrough" } });
|
||||
// O perfil vive fora do mapa `engines`: mandá-lo junto reescreveria o mapa inteiro.
|
||||
expect(puts[0]!.body.engines).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -88,7 +88,6 @@ export default defineConfig({
|
||||
"src/app/(dashboard)/dashboard/endpoint/__tests__/ApiEndpointsTab.test.tsx", // #8618 — pre-existing failure; remove this exclusion when fixed
|
||||
"tests/unit/ui/compression-combos-routing-mode-6760.test.tsx", // #8618 — pre-existing failure; remove this exclusion when fixed
|
||||
"tests/unit/ui/use-local-storage-pool-migration.test.tsx", // #8618 — pre-existing failure; remove this exclusion when fixed
|
||||
"tests/unit/ui/omniglyphContextPage.test.tsx", // #8618 — pre-existing failure; remove this exclusion when fixed
|
||||
"tests/unit/ui/waterfallInspector.test.tsx", // #8618 — pre-existing failure; remove this exclusion when fixed
|
||||
"tests/unit/ui/playground-compare-column.test.tsx", // #8618 — pre-existing failure; remove this exclusion when fixed
|
||||
"tests/unit/ui/playground-chat-tab.test.tsx", // #8618 — pre-existing failure; remove this exclusion when fixed
|
||||
|
||||
Reference in New Issue
Block a user