Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) · semantic ver

## [Unreleased]

### Added

- **feat(reliability):** named `coding-safe`, `balanced`, `aggressive` and
`passthrough` compression profiles for library callers and
`OMNIGLYPH_PROFILE` in both proxy hosts, with non-weakenable native
authority/live-state boundaries and separate Anthropic/GPT history floors.
(thanks @alteixeira20)

### Changed

- **perf(render):** harden the rendered-page LRU with canonical full SHA-256
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ bulky request block ──► profitability gate ──► reflow + render (1-bi
All off by default — the compression path above is unchanged unless you set them. `omniglyph --help` lists every flag.

- `OMNIGLYPH_GUARD_SECRETS=text|redact` — keep API keys, tokens, and other credentials out of rendered images: `text` never images a block that holds one, `redact` masks it in place. Never alters what the upstream API receives.
- `OMNIGLYPH_PROFILE=coding-safe|balanced|aggressive|passthrough` — choose a semantic compression boundary. `coding-safe` keeps authority, schemas, live tool state, and a 12-turn tail native; `balanced` protects an 8-turn tail; `aggressive` is the unchanged default; `passthrough` routes without transforming.
- `OMNIGLYPH_KEEP_SYSTEM_TEXT=1` — never image the session config (system prompt, tool docs, reminders); tool outputs and old history still convert. Guards against Anthropic's refusal classifier on system-shaped images.
- `OMNIGLYPH_MODELS` — comma-separated model bases to image (default `claude-fable-5,gpt-5.6`; `off` disables). **Grok** is supported opt-in on the OpenAI-compatible wire but stays fail-closed: text-only until acked via `OMNIGLYPH_UNVERIFIED_MODELS=grok-4.5`, pending its own reading receipt.

Expand Down
71 changes: 64 additions & 7 deletions src/core/applicability.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ export type OmniGlyphApplicabilityReason =
| 'empty_body'
| 'model_unverified';

export type OmniGlyphSafetyScope =
| 'coding-safe'
| 'balanced'
| 'aggressive'
| 'passthrough';

export interface OmniGlyphApplicabilityInput {
readonly model?: string | null;
readonly method?: string | null;
Expand Down Expand Up @@ -45,7 +51,7 @@ let runtimeModelBases: readonly string[] | null = null;
* ~2pp arithmetic, 6/15 dense-hex recall vs Fable's 100/100; GPT 5.5 likewise
* degrades on imaged history/context) — so silently imaging them is the wrong
* default. Both stay opt-in via the dashboard chips or OMNIGLYPH_MODELS. */
const DEFAULT_MODEL_BASES = ['claude-fable-5', 'gpt-5.6'];
export const DEFAULT_MODEL_BASES = ['claude-fable-5', 'gpt-5.6'] as const;

function falsey(v: string): boolean {
return /^(0|false|no|off|none)$/i.test(v.trim());
Expand All @@ -66,11 +72,50 @@ function envOrDefaultBases(): string[] {
return trimmed.split(',').map((s) => s.trim()).filter(Boolean);
}

function allowedModelBases(): string[] {
function configuredModelBases(): string[] {
if (runtimeModelBases !== null) return [...runtimeModelBases];
return envOrDefaultBases();
}

function parseSafetyScope(raw: string | undefined): OmniGlyphSafetyScope {
const value = (raw ?? '').trim().toLowerCase();
if (!value || value === 'aggressive' || value === 'legacy') return 'aggressive';
if (value === 'safe' || value === 'coding' || value === 'coding-safe') {
return 'coding-safe';
}
if (value === 'balanced') return 'balanced';
return 'passthrough';
}

function activeSafetyScope(): OmniGlyphSafetyScope {
const raw = typeof process !== 'undefined' ? process.env?.OMNIGLYPH_PROFILE : undefined;
return parseSafetyScope(raw);
}

function modelBaseMatches(id: string, candidate: string): boolean {
const target = candidate.toLowerCase();
return id === target || id.startsWith(`${target}-`);
}

function safetyAllowsConfiguredBase(
candidate: string,
scope: OmniGlyphSafetyScope,
): boolean {
if (scope === 'passthrough') return false;
if (scope === 'aggressive') return true;
const id = baseModelId(candidate).toLowerCase();
return DEFAULT_MODEL_BASES.some((safe) => modelBaseMatches(id, safe));
}

function allowedModelBasesForScope(scope: OmniGlyphSafetyScope): string[] {
return configuredModelBases().filter((candidate) =>
safetyAllowsConfiguredBase(candidate, scope));
}

function allowedModelBases(): string[] {
return allowedModelBasesForScope(activeSafetyScope());
}

/** Current effective allowed-model scope (Claude + GPT). */
export function getAllowedModelBases(): string[] {
return allowedModelBases();
Expand All @@ -89,20 +134,32 @@ export function setAllowedModelBases(list: readonly string[] | null): void {

/** Membership test against the single allowed scope. Matches exact base or `-suffix`
* alias; [variant] tags stripped first. */
function isAllowed(model: string | null | undefined): boolean {
function isAllowedForScope(
model: string | null | undefined,
scope: OmniGlyphSafetyScope,
): boolean {
if (typeof model !== 'string') return false;
const base = baseModelId(model);
return allowedModelBases().some((b) => base === b || base.startsWith(`${b}-`));
const base = baseModelId(model).toLowerCase();
return allowedModelBasesForScope(scope).some((candidate) =>
modelBaseMatches(base, candidate));
}

/** Pure model gate for an explicitly selected semantic compression profile. */
export function isOmniGlyphSupportedModelForScope(
model: string | null | undefined,
scope: OmniGlyphSafetyScope,
): boolean {
return isAllowedForScope(model, scope);
}

/** True when OmniGlyph may transform this Anthropic model. */
export function isOmniGlyphSupportedModel(model: string | null | undefined): boolean {
return isAllowed(model);
return isAllowedForScope(model, activeSafetyScope());
}

/** True when OmniGlyph may transform this GPT model. Shares the single OMNIGLYPH_MODELS scope. */
export function isOmniGlyphSupportedGptModel(model: string | null | undefined): boolean {
return isAllowed(model);
return isAllowedForScope(model, activeSafetyScope());
}

/** Canonical set of Anthropic Messages routes OmniGlyph transforms. Shared with
Expand Down
9 changes: 9 additions & 0 deletions src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ export {
getConfiguredModelBases,
isOmniGlyphSupportedGptModel,
isOmniGlyphSupportedModel,
isOmniGlyphSupportedModelForScope,
setAllowedModelBases,
shouldTransformAnthropicMessages,
type OmniGlyphApplicabilityInput,
type OmniGlyphApplicabilityReason,
type OmniGlyphSafetyScope,
} from './applicability.js';
export {
buildCountTokensBodies,
Expand All @@ -18,6 +20,7 @@ export {
export {
transformAnthropicMessages,
renderTextToImages,
type CompressionProfileName,
type OmniGlyphOptions,
type OmniGlyphReason,
type OmniGlyphTransformInput,
Expand All @@ -26,6 +29,12 @@ export {
type RenderedTextImage,
type RenderTextToImagesResult,
} from './library.js';
export {
mergeCompressionProfileOptions,
resolveCompressionProfile,
shouldKeepToolResultSharp,
type CompressionProfile,
} from './safety-policy.js';
export {
transformRequest,
type TransformInfo as OmniGlyphTransformInfo,
Expand Down
36 changes: 23 additions & 13 deletions src/core/library.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isOmniGlyphSupportedModel } from './applicability.js';
import { isOmniGlyphSupportedModelForScope } from './applicability.js';
import { countCacheControlMarkers } from './measurement.js';
import {
renderTextToPngsWithCharLimit,
Expand All @@ -19,8 +19,13 @@
type KeepSharpBlock,
type RecoverableBlock,
} from './transform.js';
import {
mergeCompressionProfileOptions,
resolveCompressionProfile,
type CompressionProfileName,
} from './safety-policy.js';

export type { KeepSharpBlock, RecoverableBlock };
export type { CompressionProfileName, KeepSharpBlock, RecoverableBlock };

Check warning on line 28 in src/core/library.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use `export…from` to re-export `RecoverableBlock`.

See more on https://sonarcloud.io/project/issues?id=diegosouzapw_omniglyph&issues=AaARIWHRLdWqOBjOrRlK&open=AaARIWHRLdWqOBjOrRlK&pullRequest=48

Check warning on line 28 in src/core/library.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use `export…from` to re-export `KeepSharpBlock`.

See more on https://sonarcloud.io/project/issues?id=diegosouzapw_omniglyph&issues=AaARIWHRLdWqOBjOrRlJ&open=AaARIWHRLdWqOBjOrRlJ&pullRequest=48

export type BytesLike = Uint8Array | ArrayBuffer | ArrayBufferView;

Expand All @@ -31,6 +36,8 @@
> {
/** Test/debug-only bypass. Product hosts should prefer their dashboard setting. */
readonly compress?: boolean;
/** Named semantic policy. Unset preserves the existing aggressive behavior. */
readonly profile?: CompressionProfileName;
}

export interface OmniGlyphTransformInput {
Expand Down Expand Up @@ -106,19 +113,22 @@
input: OmniGlyphTransformInput,
): Promise<OmniGlyphTransformResult> {
const original = toUint8Array(input.body);
if (!isOmniGlyphSupportedModel(input.model)) {
return {
body: original,
applied: false,
reason: 'unsupported_model',
detail: input.model ?? undefined,
info: emptyInfo('unsupported_model'),
cache: { ownsCacheControl: false, markerCount: countCacheControlMarkers(original) },
};
}

try {
const { body, info } = await transformRequest(original, input.options);
const { profile: requestedProfile, ...overrides } = input.options ?? {};
const profile = resolveCompressionProfile(requestedProfile);
if (!isOmniGlyphSupportedModelForScope(input.model, profile.name)) {
return {
body: original,
applied: false,
reason: 'unsupported_model',
detail: input.model ?? undefined,
info: emptyInfo('unsupported_model'),
cache: { ownsCacheControl: false, markerCount: countCacheControlMarkers(original) },
};
}
const options = mergeCompressionProfileOptions(profile, overrides);
const { body, info } = await transformRequest(original, options);
const reason = classifyReason(info);
const markerCount = countCacheControlMarkers(body);
return {
Expand Down
Loading