mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-31 04:12:10 +03:00
Merge pull request #1633 from oyi77/feat/prompt-compression-phase1
feat(compression): Phase 1 — Modular Prompt Compression Pipeline (Lite mode, 61 tests)
This commit is contained in:
23
AGENTS.md
23
AGENTS.md
@@ -132,7 +132,7 @@ All persistence uses SQLite through domain-specific modules:
|
||||
`backup.ts`, `proxies.ts`, `prompts.ts`, `webhooks.ts`, `detailedLogs.ts`,
|
||||
`domainState.ts`, `registeredKeys.ts`, `quotaSnapshots.ts`, `modelComboMappings.ts`,
|
||||
`cliToolState.ts`, `encryption.ts`, `readCache.ts`, `secrets.ts`, `stateReset.ts`,
|
||||
`contextHandoffs.ts`.
|
||||
`contextHandoffs.ts`, `compression.ts`.
|
||||
Schema migrations live in `db/migrations/` and run via `migrationRunner.ts`.
|
||||
`src/lib/localDb.ts` is a **re-export layer only** — never add logic there.
|
||||
|
||||
@@ -142,7 +142,7 @@ Schema migrations live in `db/migrations/` and run via `migrationRunner.ts`.
|
||||
journaling. `SCHEMA_SQL` defines 15 base tables. Helpers: `rowToCamel`, `encryptConnectionFields`.
|
||||
- **`migrationRunner.ts`**: Applies versioned SQL files from `db/migrations/` inside transactions.
|
||||
Tracks applied migrations in `_omniroute_migrations` table.
|
||||
- **Migrations**: 21 files (`001_initial_schema.sql` → `021_combo_call_log_targets.sql`).
|
||||
- **Migrations**: 22 files (`001_initial_schema.sql` → `022_compression_settings.sql`).
|
||||
Each migration is idempotent and runs in a transaction.
|
||||
- **Domain modules** import `getDbInstance()` from `core.ts` for all CRUD operations.
|
||||
Each module owns a specific table/set of tables (e.g., `providers.ts` → `provider_connections`,
|
||||
@@ -285,7 +285,24 @@ Includes request/response translators with helpers for image handling.
|
||||
`autoCombo/`, `intentClassifier.ts`, `taskAwareRouter.ts`, `thinkingBudget.ts`,
|
||||
`contextManager.ts`, `modelDeprecation.ts`, `modelFamilyFallback.ts`,
|
||||
`emergencyFallback.ts`, `workflowFSM.ts`, `backgroundTaskDetector.ts`, `ipFilter.ts`,
|
||||
`signatureCache.ts`, `volumeDetector.ts`, `contextHandoff.ts`, and more.
|
||||
`signatureCache.ts`, `volumeDetector.ts`, `contextHandoff.ts`, `compression/` (prompt
|
||||
compression pipeline), and more.
|
||||
|
||||
#### Prompt Compression Pipeline (`compression/`)
|
||||
|
||||
Modular prompt compression that runs proactively before the existing reactive context manager.
|
||||
|
||||
- **`strategySelector.ts`**: Selects compression mode based on config, combo overrides, auto-trigger
|
||||
thresholds. Priority: combo override > auto-trigger > default mode > off.
|
||||
- **`lite.ts`**: 5 lite-mode techniques: `collapseWhitespace`, `dedupSystemPrompt`,
|
||||
`compressToolResults`, `removeRedundantContent`, `replaceImageUrls`. Target: 10-15% savings at
|
||||
<1ms latency.
|
||||
- **`stats.ts`**: Per-request compression stats tracking (original tokens, compressed tokens,
|
||||
savings %, techniques used).
|
||||
- **`types.ts`**: `CompressionMode` (off/lite/standard/aggressive/ultra), `CompressionConfig`,
|
||||
`CompressionStats`, `CompressionResult`.
|
||||
- DB settings in `src/lib/db/compression.ts`, API route at `src/app/api/settings/compression/`.
|
||||
- Phase 1 implements lite mode only; standard/aggressive/ultra are placeholders for Phase 2.
|
||||
|
||||
#### Combo Routing Engine (`combo.ts`)
|
||||
|
||||
|
||||
@@ -53,7 +53,9 @@ function getRuntimePlatform(): string {
|
||||
}
|
||||
|
||||
function getRuntimeArch(): string {
|
||||
return typeof process !== "undefined" && typeof process.arch === "string" ? process.arch : "unknown";
|
||||
return typeof process !== "undefined" && typeof process.arch === "string"
|
||||
? process.arch
|
||||
: "unknown";
|
||||
}
|
||||
|
||||
function getRuntimeVersion(): string {
|
||||
|
||||
@@ -1560,7 +1560,38 @@ export async function handleChatCore({
|
||||
const allMessages =
|
||||
body?.messages || body?.input || body?.contents || body?.request?.contents || [];
|
||||
if (body && Array.isArray(allMessages) && allMessages.length > 0) {
|
||||
const estimatedTokens = estimateTokens(JSON.stringify(allMessages));
|
||||
let estimatedTokens = estimateTokens(JSON.stringify(allMessages));
|
||||
|
||||
// --- Modular Compression Pipeline (Phase 1: Lite mode) ---
|
||||
// Runs BEFORE the existing reactive compressContext() to proactively reduce tokens.
|
||||
try {
|
||||
const { getCompressionSettings } = await import("../../src/lib/db/compression.ts");
|
||||
const { selectCompressionStrategy, applyCompression } =
|
||||
await import("../services/compression/strategySelector.ts");
|
||||
const { trackCompressionStats } = await import("../services/compression/stats.ts");
|
||||
const config = await getCompressionSettings();
|
||||
const mode = selectCompressionStrategy(config, comboName ?? null, estimatedTokens);
|
||||
if (mode !== "off") {
|
||||
const result = applyCompression(body, mode, { model: effectiveModel });
|
||||
if (result.compressed && result.stats) {
|
||||
body = result.body as typeof body;
|
||||
estimatedTokens = result.stats.compressedTokens;
|
||||
trackCompressionStats(result.stats);
|
||||
log?.info?.(
|
||||
"COMPRESSION",
|
||||
`Prompt compressed (${mode}): ${result.stats.originalTokens} -> ${result.stats.compressedTokens} tokens (${result.stats.savingsPercent}% saved, techniques: ${result.stats.techniquesUsed.join(",")})`
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
log?.warn?.(
|
||||
"COMPRESSION",
|
||||
"Compression pipeline error (non-fatal): " +
|
||||
(err instanceof Error ? err.message : String(err))
|
||||
);
|
||||
}
|
||||
// --- End Modular Compression Pipeline ---
|
||||
|
||||
let contextLimit = getTokenLimit(provider, effectiveModel);
|
||||
|
||||
if (isCombo && comboName) {
|
||||
|
||||
@@ -48,6 +48,16 @@
|
||||
- **`volumeDetector.ts`** — Detects request volume spikes; triggers rate-limit escalation or load-shedding.
|
||||
- **`contextHandoff.ts`** — Serializes/restores session context for agent handoff (A2A protocol).
|
||||
|
||||
### Prompt Compression Pipeline
|
||||
|
||||
- **`compression/`** — Modular prompt compression running proactively before `contextManager.ts`.
|
||||
- `strategySelector.ts` — Selects mode (off/lite/standard/aggressive/ultra) with combo overrides and auto-trigger.
|
||||
- `lite.ts` — 5 lite techniques: whitespace collapse, system prompt dedup, tool result truncation, redundant removal, image URL placeholder.
|
||||
- `stats.ts` — Per-request compression stats (original/compressed tokens, savings %, techniques).
|
||||
- `types.ts` — Shared types (`CompressionMode`, `CompressionConfig`, `CompressionStats`, `CompressionResult`).
|
||||
- `index.ts` — Barrel re-exports.
|
||||
- Phase 1: lite mode only. Standard/aggressive/ultra = Phase 2.
|
||||
|
||||
### Auto-Routing & Adaptive
|
||||
|
||||
- **`autoCombo/`** — Auto-generates combo configs based on historical performance, cost, and latency.
|
||||
|
||||
@@ -67,10 +67,7 @@ function evictExpired(now = Date.now()): void {
|
||||
function evictUntilWithinLimits(maxBytes: number, incomingBytes: number): void {
|
||||
// Drop oldest until both the entry-count and total-byte caps are satisfied.
|
||||
// Map iteration is insertion-ordered so the first key is the oldest entry.
|
||||
while (
|
||||
(cache.size >= MAX_ENTRIES || cacheBytes + incomingBytes > maxBytes) &&
|
||||
cache.size > 0
|
||||
) {
|
||||
while ((cache.size >= MAX_ENTRIES || cacheBytes + incomingBytes > maxBytes) && cache.size > 0) {
|
||||
const firstKey = cache.keys().next().value;
|
||||
if (!firstKey) break;
|
||||
deleteEntry(firstKey);
|
||||
@@ -122,9 +119,7 @@ export function getChatGptImageConversationContext(
|
||||
* saved chatgpt.com conversation node and actually edit the image instead
|
||||
* of generating an unrelated one from scratch.
|
||||
*/
|
||||
export function findChatGptImageBySha256(
|
||||
hash: string
|
||||
): { id: string; entry: CachedImage } | null {
|
||||
export function findChatGptImageBySha256(hash: string): { id: string; entry: CachedImage } | null {
|
||||
evictExpired();
|
||||
const target = hash.toLowerCase();
|
||||
for (const [id, entry] of cache.entries()) {
|
||||
|
||||
31
open-sse/services/compression/index.ts
Normal file
31
open-sse/services/compression/index.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Compression Pipeline — Phase 1 (Lite mode)
|
||||
*
|
||||
* Modular prompt compression that runs BEFORE the existing reactive context manager.
|
||||
* Lite mode: 5 techniques, ~10-15% token savings, <1ms latency.
|
||||
*/
|
||||
|
||||
// Types
|
||||
export type {
|
||||
CompressionMode,
|
||||
CompressionConfig,
|
||||
CompressionStats,
|
||||
CompressionResult,
|
||||
} from "./types";
|
||||
export { DEFAULT_COMPRESSION_CONFIG } from "./types";
|
||||
|
||||
// Lite compression techniques
|
||||
export {
|
||||
applyLiteCompression,
|
||||
collapseWhitespace,
|
||||
dedupSystemPrompt,
|
||||
compressToolResults,
|
||||
removeRedundantContent,
|
||||
replaceImageUrls,
|
||||
} from "./lite";
|
||||
|
||||
// Strategy selector
|
||||
export { selectCompressionStrategy, getEffectiveMode, applyCompression } from "./strategySelector";
|
||||
|
||||
// Stats tracking
|
||||
export { estimateCompressionTokens, createCompressionStats, trackCompressionStats } from "./stats";
|
||||
170
open-sse/services/compression/lite.ts
Normal file
170
open-sse/services/compression/lite.ts
Normal file
@@ -0,0 +1,170 @@
|
||||
import type { CompressionResult, CompressionMode } from "./types";
|
||||
import { createCompressionStats } from "./stats";
|
||||
|
||||
interface Message {
|
||||
role: string;
|
||||
content: string | Array<Record<string, unknown>>;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
interface ChatBody {
|
||||
messages?: Message[];
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export function collapseWhitespace(body: ChatBody): {
|
||||
body: ChatBody;
|
||||
applied: boolean;
|
||||
} {
|
||||
if (!body.messages) return { body, applied: false };
|
||||
let applied = false;
|
||||
const messages = body.messages.map((msg) => {
|
||||
if (typeof msg.content !== "string") return msg;
|
||||
const normalized = msg.content.replace(/\n{3,}/g, "\n\n").replace(/[ \t]+$/gm, "");
|
||||
if (normalized !== msg.content) applied = true;
|
||||
return { ...msg, content: normalized };
|
||||
});
|
||||
return { body: { ...body, messages }, applied };
|
||||
}
|
||||
|
||||
export function dedupSystemPrompt(body: ChatBody): {
|
||||
body: ChatBody;
|
||||
applied: boolean;
|
||||
} {
|
||||
if (!body.messages) return { body, applied: false };
|
||||
const seen = new Set<string>();
|
||||
let applied = false;
|
||||
const messages = body.messages.filter((msg) => {
|
||||
if (msg.role !== "system" || typeof msg.content !== "string") return true;
|
||||
const key = msg.content.trim().slice(0, 200);
|
||||
if (seen.has(key)) {
|
||||
applied = true;
|
||||
return false;
|
||||
}
|
||||
seen.add(key);
|
||||
return true;
|
||||
});
|
||||
return { body: { ...body, messages }, applied };
|
||||
}
|
||||
|
||||
export function compressToolResults(body: ChatBody): {
|
||||
body: ChatBody;
|
||||
applied: boolean;
|
||||
} {
|
||||
if (!body.messages) return { body, applied: false };
|
||||
const MAX_TOOL_LENGTH = 2000;
|
||||
let applied = false;
|
||||
const messages = body.messages.map((msg) => {
|
||||
if (msg.role !== "tool" || typeof msg.content !== "string") return msg;
|
||||
if (msg.content.length <= MAX_TOOL_LENGTH) return msg;
|
||||
applied = true;
|
||||
return {
|
||||
...msg,
|
||||
content: msg.content.slice(0, MAX_TOOL_LENGTH) + "\n...[truncated]",
|
||||
};
|
||||
});
|
||||
return { body: { ...body, messages }, applied };
|
||||
}
|
||||
|
||||
export function removeRedundantContent(body: ChatBody): {
|
||||
body: ChatBody;
|
||||
applied: boolean;
|
||||
} {
|
||||
if (!body.messages) return { body, applied: false };
|
||||
let applied = false;
|
||||
const messages: Message[] = [];
|
||||
for (let i = 0; i < body.messages.length; i++) {
|
||||
const msg = body.messages[i];
|
||||
const contentStr = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
|
||||
if (
|
||||
i > 0 &&
|
||||
body.messages[i - 1].role === msg.role &&
|
||||
typeof body.messages[i - 1].content === "string" &&
|
||||
body.messages[i - 1].content === contentStr
|
||||
) {
|
||||
applied = true;
|
||||
continue;
|
||||
}
|
||||
messages.push(msg);
|
||||
}
|
||||
return { body: { ...body, messages }, applied };
|
||||
}
|
||||
|
||||
export function replaceImageUrls(
|
||||
body: ChatBody,
|
||||
model?: string
|
||||
): { body: ChatBody; applied: boolean } {
|
||||
if (!body.messages) return { body, applied: false };
|
||||
const isVisionModel = model && /\b(vision|gpt-4o|claude-3|gemini-1\.5|gemini-2)\b/i.test(model);
|
||||
if (isVisionModel) return { body, applied: false };
|
||||
let applied = false;
|
||||
const messages = body.messages.map((msg) => {
|
||||
if (!Array.isArray(msg.content)) return msg;
|
||||
const newContent = msg.content.map((part) => {
|
||||
if (
|
||||
typeof part === "object" &&
|
||||
part !== null &&
|
||||
part.type === "image_url" &&
|
||||
typeof (part as Record<string, unknown>).image_url === "object" &&
|
||||
((part as Record<string, unknown>).image_url as Record<string, unknown>)?.url
|
||||
) {
|
||||
const url = String(
|
||||
((part as Record<string, unknown>).image_url as Record<string, unknown>).url
|
||||
);
|
||||
if (url.startsWith("data:image/")) {
|
||||
applied = true;
|
||||
const format = url.slice(url.indexOf("/") + 1, url.indexOf(";")) || "unknown";
|
||||
return { type: "text", text: `[image: ${format}]` };
|
||||
}
|
||||
}
|
||||
return part;
|
||||
});
|
||||
return { ...msg, content: newContent };
|
||||
});
|
||||
return { body: { ...body, messages }, applied };
|
||||
}
|
||||
|
||||
export function applyLiteCompression(
|
||||
body: Record<string, unknown>,
|
||||
options?: { model?: string }
|
||||
): CompressionResult {
|
||||
const originalBody = body;
|
||||
let current = body as ChatBody;
|
||||
const techniquesApplied: string[] = [];
|
||||
|
||||
const r1 = collapseWhitespace(current);
|
||||
current = r1.body;
|
||||
if (r1.applied) techniquesApplied.push("whitespace");
|
||||
|
||||
const r2 = dedupSystemPrompt(current);
|
||||
current = r2.body;
|
||||
if (r2.applied) techniquesApplied.push("system-dedup");
|
||||
|
||||
const r3 = compressToolResults(current);
|
||||
current = r3.body;
|
||||
if (r3.applied) techniquesApplied.push("tool-compress");
|
||||
|
||||
const r4 = removeRedundantContent(current);
|
||||
current = r4.body;
|
||||
if (r4.applied) techniquesApplied.push("redundant-remove");
|
||||
|
||||
const r5 = replaceImageUrls(current, options?.model);
|
||||
current = r5.body;
|
||||
if (r5.applied) techniquesApplied.push("image-placeholder");
|
||||
|
||||
const compressed = techniquesApplied.length > 0;
|
||||
const stats = compressed
|
||||
? createCompressionStats(
|
||||
originalBody,
|
||||
current as Record<string, unknown>,
|
||||
"lite",
|
||||
techniquesApplied
|
||||
)
|
||||
: null;
|
||||
|
||||
return {
|
||||
body: current as Record<string, unknown>,
|
||||
compressed,
|
||||
stats,
|
||||
};
|
||||
}
|
||||
38
open-sse/services/compression/stats.ts
Normal file
38
open-sse/services/compression/stats.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import type { CompressionStats, CompressionMode } from "./types";
|
||||
|
||||
const CHARS_PER_TOKEN = 4;
|
||||
|
||||
export function estimateCompressionTokens(text: string | object | null | undefined): number {
|
||||
if (!text) return 0;
|
||||
const str = typeof text === "string" ? text : JSON.stringify(text);
|
||||
return Math.ceil(str.length / CHARS_PER_TOKEN);
|
||||
}
|
||||
|
||||
export function createCompressionStats(
|
||||
originalBody: Record<string, unknown>,
|
||||
compressedBody: Record<string, unknown>,
|
||||
mode: CompressionMode,
|
||||
techniquesUsed: string[]
|
||||
): CompressionStats {
|
||||
const originalTokens = estimateCompressionTokens(originalBody);
|
||||
const compressedTokens = estimateCompressionTokens(compressedBody);
|
||||
const savingsPercent =
|
||||
originalTokens > 0
|
||||
? Math.round(((originalTokens - compressedTokens) / originalTokens) * 10000) / 100
|
||||
: 0;
|
||||
return {
|
||||
originalTokens,
|
||||
compressedTokens,
|
||||
savingsPercent,
|
||||
techniquesUsed,
|
||||
mode,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
}
|
||||
|
||||
export function trackCompressionStats(stats: CompressionStats): void {
|
||||
if (stats.originalTokens <= 0) return;
|
||||
console.log(
|
||||
`[COMPRESSION] mode=${stats.mode} tokens=${stats.originalTokens}->${stats.compressedTokens} savings=${stats.savingsPercent}% techniques=${stats.techniquesUsed.join(",")}`
|
||||
);
|
||||
}
|
||||
51
open-sse/services/compression/strategySelector.ts
Normal file
51
open-sse/services/compression/strategySelector.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import type { CompressionConfig, CompressionMode, CompressionResult } from "./types";
|
||||
import { applyLiteCompression } from "./lite";
|
||||
|
||||
export function checkComboOverride(
|
||||
config: CompressionConfig,
|
||||
comboId: string | null
|
||||
): CompressionMode | null {
|
||||
if (!comboId || !config.comboOverrides) return null;
|
||||
return config.comboOverrides[comboId] ?? null;
|
||||
}
|
||||
|
||||
export function shouldAutoTrigger(config: CompressionConfig, estimatedTokens: number): boolean {
|
||||
return config.autoTriggerTokens > 0 && estimatedTokens >= config.autoTriggerTokens;
|
||||
}
|
||||
|
||||
export function getEffectiveMode(
|
||||
config: CompressionConfig,
|
||||
comboId: string | null,
|
||||
estimatedTokens: number
|
||||
): CompressionMode {
|
||||
if (!config.enabled) return "off";
|
||||
|
||||
const comboMode = checkComboOverride(config, comboId);
|
||||
if (comboMode) return comboMode;
|
||||
|
||||
if (shouldAutoTrigger(config, estimatedTokens)) return "lite";
|
||||
|
||||
return config.defaultMode;
|
||||
}
|
||||
|
||||
export function selectCompressionStrategy(
|
||||
config: CompressionConfig,
|
||||
comboId: string | null,
|
||||
estimatedTokens: number
|
||||
): CompressionMode {
|
||||
return getEffectiveMode(config, comboId, estimatedTokens);
|
||||
}
|
||||
|
||||
export function applyCompression(
|
||||
body: Record<string, unknown>,
|
||||
mode: CompressionMode,
|
||||
options?: { model?: string }
|
||||
): CompressionResult {
|
||||
if (mode === "off") {
|
||||
return { body, compressed: false, stats: null };
|
||||
}
|
||||
if (mode === "lite") {
|
||||
return applyLiteCompression(body, options);
|
||||
}
|
||||
return { body, compressed: false, stats: null };
|
||||
}
|
||||
45
open-sse/services/compression/types.ts
Normal file
45
open-sse/services/compression/types.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Compression Pipeline Types — Phase 1 (Lite mode)
|
||||
*
|
||||
* Modular prompt compression that runs BEFORE the existing reactive context manager.
|
||||
*/
|
||||
|
||||
/** Compression mode levels (only 'off' and 'lite' are active in Phase 1) */
|
||||
export type CompressionMode = "off" | "lite" | "standard" | "aggressive" | "ultra";
|
||||
|
||||
/** Compression configuration stored in DB */
|
||||
export interface CompressionConfig {
|
||||
enabled: boolean;
|
||||
defaultMode: CompressionMode;
|
||||
autoTriggerTokens: number;
|
||||
cacheMinutes: number;
|
||||
preserveSystemPrompt: boolean;
|
||||
comboOverrides: Record<string, CompressionMode>;
|
||||
}
|
||||
|
||||
/** Per-request compression statistics */
|
||||
export interface CompressionStats {
|
||||
originalTokens: number;
|
||||
compressedTokens: number;
|
||||
savingsPercent: number;
|
||||
techniquesUsed: string[];
|
||||
mode: CompressionMode;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
/** Result of a compression operation */
|
||||
export interface CompressionResult {
|
||||
body: Record<string, unknown>;
|
||||
compressed: boolean;
|
||||
stats: CompressionStats | null;
|
||||
}
|
||||
|
||||
/** Default compression config values */
|
||||
export const DEFAULT_COMPRESSION_CONFIG: CompressionConfig = {
|
||||
enabled: false,
|
||||
defaultMode: "off",
|
||||
autoTriggerTokens: 0,
|
||||
cacheMinutes: 5,
|
||||
preserveSystemPrompt: true,
|
||||
comboOverrides: {},
|
||||
};
|
||||
@@ -1230,7 +1230,11 @@ async function fetchAntigravityAvailableModelsCached(
|
||||
|
||||
const cacheKey = buildAntigravityUsageCacheKey(accessToken, projectId);
|
||||
const cached = _antigravityAvailableModelsCache.get(cacheKey);
|
||||
if (!options.forceRefresh && cached && Date.now() - cached.fetchedAt < ANTIGRAVITY_MODELS_CACHE_TTL_MS) {
|
||||
if (
|
||||
!options.forceRefresh &&
|
||||
cached &&
|
||||
Date.now() - cached.fetchedAt < ANTIGRAVITY_MODELS_CACHE_TTL_MS
|
||||
) {
|
||||
return cached.data;
|
||||
}
|
||||
|
||||
@@ -1361,25 +1365,31 @@ async function probeAntigravityCreditBalance(
|
||||
|
||||
const cacheKey = buildAntigravityUsageCacheKey(accessToken, projectId || accountId);
|
||||
const cached = _antigravityCreditProbeCache.get(cacheKey);
|
||||
if (!options.forceRefresh && cached && Date.now() - cached.fetchedAt < ANTIGRAVITY_CREDIT_PROBE_TTL_MS) {
|
||||
if (
|
||||
!options.forceRefresh &&
|
||||
cached &&
|
||||
Date.now() - cached.fetchedAt < ANTIGRAVITY_CREDIT_PROBE_TTL_MS
|
||||
) {
|
||||
return cached.data;
|
||||
}
|
||||
|
||||
const inflight = _antigravityCreditProbeInflight.get(cacheKey);
|
||||
if (inflight) return inflight;
|
||||
|
||||
const promise = probeAntigravityCreditBalanceUncached(accessToken, accountId, projectId).then(
|
||||
(data) => {
|
||||
_antigravityCreditProbeCache.set(cacheKey, { data, fetchedAt: Date.now() });
|
||||
return data;
|
||||
},
|
||||
(error) => {
|
||||
_antigravityCreditProbeCache.set(cacheKey, { data: null, fetchedAt: Date.now() });
|
||||
throw error;
|
||||
}
|
||||
).finally(() => {
|
||||
_antigravityCreditProbeInflight.delete(cacheKey);
|
||||
});
|
||||
const promise = probeAntigravityCreditBalanceUncached(accessToken, accountId, projectId)
|
||||
.then(
|
||||
(data) => {
|
||||
_antigravityCreditProbeCache.set(cacheKey, { data, fetchedAt: Date.now() });
|
||||
return data;
|
||||
},
|
||||
(error) => {
|
||||
_antigravityCreditProbeCache.set(cacheKey, { data: null, fetchedAt: Date.now() });
|
||||
throw error;
|
||||
}
|
||||
)
|
||||
.finally(() => {
|
||||
_antigravityCreditProbeInflight.delete(cacheKey);
|
||||
});
|
||||
|
||||
_antigravityCreditProbeInflight.set(cacheKey, promise);
|
||||
return promise;
|
||||
@@ -1501,7 +1511,12 @@ async function getAntigravityUsage(
|
||||
// If no cached balance and credits mode is enabled, fire a minimal probe
|
||||
const creditsMode = getCreditsMode();
|
||||
if ((options.forceRefresh || creditBalance === null) && creditsMode !== "off") {
|
||||
creditBalance = await probeAntigravityCreditBalance(accessToken, accountId, projectId, options);
|
||||
creditBalance = await probeAntigravityCreditBalance(
|
||||
accessToken,
|
||||
accountId,
|
||||
projectId,
|
||||
options
|
||||
);
|
||||
}
|
||||
|
||||
const data = await fetchAntigravityAvailableModelsCached(accessToken, projectId, options);
|
||||
|
||||
@@ -183,15 +183,12 @@ export default function ApiManagerPageClient() {
|
||||
|
||||
for (const key of apiKeys) {
|
||||
// Match analytics entry by key name (reliable across both systems)
|
||||
const analyticsMatch = byApiKey.find(
|
||||
(entry: any) => entry.apiKeyName === key.name
|
||||
);
|
||||
const analyticsMatch = byApiKey.find((entry: any) => entry.apiKeyName === key.name);
|
||||
|
||||
// The call-logs endpoint returns entries sorted by timestamp DESC,
|
||||
// so the first match is the most recent one.
|
||||
const lastUsed = (logs || []).find(
|
||||
(log: any) => log.apiKeyName === key.name
|
||||
)?.timestamp || null;
|
||||
const lastUsed =
|
||||
(logs || []).find((log: any) => log.apiKeyName === key.name)?.timestamp || null;
|
||||
|
||||
stats[key.id] = {
|
||||
totalRequests: analyticsMatch?.requests ?? 0,
|
||||
|
||||
@@ -10,11 +10,7 @@ import {
|
||||
} from "@/lib/auth/managementPassword";
|
||||
import { loginSchema } from "@/shared/validation/schemas";
|
||||
import { isValidationFailure, validateBody } from "@/shared/validation/helpers";
|
||||
import {
|
||||
checkLoginGuard,
|
||||
clearLoginAttempts,
|
||||
recordLoginFailure,
|
||||
} from "@/server/auth/loginGuard";
|
||||
import { checkLoginGuard, clearLoginAttempts, recordLoginFailure } from "@/server/auth/loginGuard";
|
||||
|
||||
// SECURITY: No hardcoded fallback — JWT_SECRET must be configured.
|
||||
if (!process.env.JWT_SECRET) {
|
||||
|
||||
@@ -65,7 +65,10 @@ import {
|
||||
|
||||
type JsonRecord = Record<string, unknown>;
|
||||
|
||||
const antigravityDiscoveryInflight = new Map<string, Promise<Array<{ id: string; name: string }>>>();
|
||||
const antigravityDiscoveryInflight = new Map<
|
||||
string,
|
||||
Promise<Array<{ id: string; name: string }>>
|
||||
>();
|
||||
|
||||
function asRecord(value: unknown): JsonRecord {
|
||||
return value && typeof value === "object" && !Array.isArray(value) ? (value as JsonRecord) : {};
|
||||
@@ -1636,7 +1639,11 @@ export async function GET(
|
||||
});
|
||||
}
|
||||
|
||||
const remoteModels = await fetchAntigravityDiscoveryModelsCached(accessToken, connectionId, proxy);
|
||||
const remoteModels = await fetchAntigravityDiscoveryModelsCached(
|
||||
accessToken,
|
||||
connectionId,
|
||||
proxy
|
||||
);
|
||||
if (remoteModels.length > 0) {
|
||||
return buildApiDiscoveryResponse(remoteModels);
|
||||
}
|
||||
|
||||
58
src/app/api/settings/compression/route.ts
Normal file
58
src/app/api/settings/compression/route.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { z } from "zod";
|
||||
import { isAuthenticated } from "@/shared/utils/apiAuth";
|
||||
import { isValidationFailure, validateBody } from "@/shared/validation/helpers";
|
||||
import { getCompressionSettings, updateCompressionSettings } from "@/lib/db/compression";
|
||||
|
||||
const compressionModeSchema = z.enum(["off", "lite", "standard", "aggressive", "ultra"]);
|
||||
|
||||
const compressionSettingsUpdateSchema = z
|
||||
.object({
|
||||
enabled: z.boolean().optional(),
|
||||
defaultMode: compressionModeSchema.optional(),
|
||||
autoTriggerTokens: z.number().int().min(0).optional(),
|
||||
cacheMinutes: z.number().int().min(1).max(60).optional(),
|
||||
preserveSystemPrompt: z.boolean().optional(),
|
||||
comboOverrides: z.record(z.string(), compressionModeSchema).optional(),
|
||||
})
|
||||
.strict();
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
if (!(await isAuthenticated(request))) {
|
||||
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
}
|
||||
|
||||
try {
|
||||
const settings = await getCompressionSettings();
|
||||
return NextResponse.json(settings);
|
||||
} catch (error) {
|
||||
return NextResponse.json({ error: String(error) }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
export async function PUT(request: NextRequest) {
|
||||
if (!(await isAuthenticated(request))) {
|
||||
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
}
|
||||
|
||||
try {
|
||||
let rawBody: unknown;
|
||||
try {
|
||||
rawBody = await request.json();
|
||||
} catch {
|
||||
return NextResponse.json({ error: "Invalid JSON body" }, { status: 400 });
|
||||
}
|
||||
|
||||
const validation = validateBody(compressionSettingsUpdateSchema, rawBody);
|
||||
if (isValidationFailure(validation)) {
|
||||
return NextResponse.json({ error: validation.error }, { status: 400 });
|
||||
}
|
||||
|
||||
const settings = await updateCompressionSettings(
|
||||
validation.data as Parameters<typeof updateCompressionSettings>[0]
|
||||
);
|
||||
return NextResponse.json(settings);
|
||||
} catch (error) {
|
||||
return NextResponse.json({ error: String(error) }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -140,10 +140,15 @@ export async function GET(request: Request) {
|
||||
// ── Enrich entries with missing apiKeyName ──────────────────────────
|
||||
try {
|
||||
// Only run enrichment if there are actually NULL entries
|
||||
const hasNull = db.prepare("SELECT 1 FROM usage_history WHERE (api_key_name IS NULL OR api_key_name = '') AND connection_id IS NOT NULL LIMIT 1").get();
|
||||
const hasNull = db
|
||||
.prepare(
|
||||
"SELECT 1 FROM usage_history WHERE (api_key_name IS NULL OR api_key_name = '') AND connection_id IS NOT NULL LIMIT 1"
|
||||
)
|
||||
.get();
|
||||
if (hasNull) {
|
||||
// Step 1: dominant key per connectionId from existing usage data
|
||||
db.prepare(`
|
||||
db.prepare(
|
||||
`
|
||||
UPDATE usage_history
|
||||
SET
|
||||
api_key_name = (
|
||||
@@ -171,22 +176,29 @@ export async function GET(request: Request) {
|
||||
WHERE uh3.connection_id = usage_history.connection_id
|
||||
AND uh3.api_key_name IS NOT NULL AND uh3.api_key_name != ''
|
||||
)
|
||||
`).run();
|
||||
`
|
||||
).run();
|
||||
|
||||
// Step 2 & 3: For still unresolved connections, check apiKeys config
|
||||
const stillNull = db.prepare("SELECT DISTINCT connection_id FROM usage_history WHERE (api_key_name IS NULL OR api_key_name = '') AND connection_id IS NOT NULL").all();
|
||||
const stillNull = db
|
||||
.prepare(
|
||||
"SELECT DISTINCT connection_id FROM usage_history WHERE (api_key_name IS NULL OR api_key_name = '') AND connection_id IS NOT NULL"
|
||||
)
|
||||
.all();
|
||||
if (stillNull.length > 0) {
|
||||
const { getApiKeys } = await import("@/lib/localDb");
|
||||
const apiKeys = (await getApiKeys()) as any[];
|
||||
|
||||
const updateStmt = db.prepare("UPDATE usage_history SET api_key_name = ?, api_key_id = ? WHERE connection_id = ? AND (api_key_name IS NULL OR api_key_name = '')");
|
||||
|
||||
const updateStmt = db.prepare(
|
||||
"UPDATE usage_history SET api_key_name = ?, api_key_id = ? WHERE connection_id = ? AND (api_key_name IS NULL OR api_key_name = '')"
|
||||
);
|
||||
const updateMany = db.transaction((updates: any[]) => {
|
||||
for (const u of updates) updateStmt.run(u.name, u.id, u.cid);
|
||||
});
|
||||
|
||||
|
||||
const updates = [];
|
||||
const orphanIds = new Set(stillNull.map((r: any) => r.connection_id));
|
||||
|
||||
|
||||
for (const ak of apiKeys) {
|
||||
const allowed = Array.isArray(ak.allowedConnections) ? ak.allowedConnections : [];
|
||||
const keyName = ak.name || ak.id;
|
||||
@@ -198,17 +210,23 @@ export async function GET(request: Request) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (orphanIds.size > 0) {
|
||||
const unrestrictedKeys = apiKeys.filter(
|
||||
(ak: any) => !Array.isArray(ak.allowedConnections) || ak.allowedConnections.length === 0
|
||||
(ak: any) =>
|
||||
!Array.isArray(ak.allowedConnections) || ak.allowedConnections.length === 0
|
||||
);
|
||||
if (unrestrictedKeys.length > 0) {
|
||||
let bestKey = unrestrictedKeys[0];
|
||||
let bestCount = -1;
|
||||
for (const uk of unrestrictedKeys) {
|
||||
const countRow = db.prepare("SELECT COUNT(*) as c FROM usage_history WHERE api_key_name = ?").get(uk.name || uk.id) as any;
|
||||
if (countRow.c > bestCount) { bestCount = countRow.c; bestKey = uk; }
|
||||
const countRow = db
|
||||
.prepare("SELECT COUNT(*) as c FROM usage_history WHERE api_key_name = ?")
|
||||
.get(uk.name || uk.id) as any;
|
||||
if (countRow.c > bestCount) {
|
||||
bestCount = countRow.c;
|
||||
bestKey = uk;
|
||||
}
|
||||
}
|
||||
const fallbackName = bestKey.name || bestKey.id;
|
||||
const fallbackId = bestKey.id || null;
|
||||
@@ -217,11 +235,11 @@ export async function GET(request: Request) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (updates.length > 0) updateMany(updates);
|
||||
}
|
||||
}
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
console.error("Failed to backfill missing api_key_name:", e);
|
||||
}
|
||||
|
||||
@@ -322,13 +340,15 @@ export async function GET(request: Request) {
|
||||
heatmapStart.setTime(customStart.getTime());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Heatmap needs its own whereClause if api keys are filtered
|
||||
const heatmapConditions = ["timestamp >= @heatmapStart"];
|
||||
if (apiKeyWhere) heatmapConditions.push(apiKeyWhere);
|
||||
const heatmapParams: Record<string, string> = { heatmapStart: heatmapStart.toISOString() };
|
||||
if (apiKeyIds.length > 0) {
|
||||
apiKeyIds.forEach((key, i) => { heatmapParams[`apiKey${i}`] = key; });
|
||||
apiKeyIds.forEach((key, i) => {
|
||||
heatmapParams[`apiKey${i}`] = key;
|
||||
});
|
||||
}
|
||||
|
||||
const heatmapRows = db
|
||||
@@ -535,7 +555,7 @@ export async function GET(request: Request) {
|
||||
)
|
||||
: 0,
|
||||
avgLatencyMs: Math.round(Number(summaryRow?.avgLatencyMs || 0)),
|
||||
totalCost: 0,
|
||||
totalCost: 0,
|
||||
firstRequest: summaryRow?.firstRequest || "",
|
||||
lastRequest: summaryRow?.lastRequest || "",
|
||||
fallbackCount: Number(fallbackRow?.fallbacks || 0),
|
||||
@@ -772,10 +792,17 @@ export async function GET(request: Request) {
|
||||
const presetSinceIso = getRangeStartIso(presetRange);
|
||||
const presetConditions = [];
|
||||
const presetParams: Record<string, string> = {};
|
||||
if (presetSinceIso) { presetConditions.push("timestamp >= @presetSince"); presetParams.presetSince = presetSinceIso; }
|
||||
if (apiKeyWhere) { presetConditions.push(apiKeyWhere); Object.assign(presetParams, params); }
|
||||
if (presetSinceIso) {
|
||||
presetConditions.push("timestamp >= @presetSince");
|
||||
presetParams.presetSince = presetSinceIso;
|
||||
}
|
||||
if (apiKeyWhere) {
|
||||
presetConditions.push(apiKeyWhere);
|
||||
Object.assign(presetParams, params);
|
||||
}
|
||||
|
||||
const presetWhere = presetConditions.length > 0 ? `WHERE ${presetConditions.join(" AND ")}` : "";
|
||||
const presetWhere =
|
||||
presetConditions.length > 0 ? `WHERE ${presetConditions.join(" AND ")}` : "";
|
||||
|
||||
const presetModelRows = db
|
||||
.prepare(
|
||||
|
||||
50
src/lib/db/compression.ts
Normal file
50
src/lib/db/compression.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { getDbInstance } from "./core";
|
||||
import { backupDbFile } from "./backup";
|
||||
|
||||
const DEFAULT_COMPRESSION_CONFIG = {
|
||||
enabled: false,
|
||||
defaultMode: "off" as const,
|
||||
autoTriggerTokens: 0,
|
||||
cacheMinutes: 5,
|
||||
preserveSystemPrompt: true,
|
||||
comboOverrides: {},
|
||||
};
|
||||
|
||||
type CompressionConfig = typeof DEFAULT_COMPRESSION_CONFIG & {
|
||||
comboOverrides: Record<string, "off" | "lite" | "standard" | "aggressive" | "ultra">;
|
||||
};
|
||||
|
||||
export async function getCompressionSettings(): Promise<CompressionConfig> {
|
||||
const db = getDbInstance();
|
||||
const rows = db.prepare("SELECT key, value FROM key_value WHERE namespace = 'compression'").all();
|
||||
const settings: Record<string, unknown> = { ...DEFAULT_COMPRESSION_CONFIG };
|
||||
for (const row of rows) {
|
||||
const record = row as Record<string, unknown>;
|
||||
const key = typeof record.key === "string" ? record.key : null;
|
||||
const rawValue = typeof record.value === "string" ? record.value : null;
|
||||
if (!key || rawValue === null) continue;
|
||||
try {
|
||||
settings[key] = JSON.parse(rawValue);
|
||||
} catch {
|
||||
// skip malformed JSON
|
||||
}
|
||||
}
|
||||
return settings as CompressionConfig;
|
||||
}
|
||||
|
||||
export async function updateCompressionSettings(
|
||||
updates: Partial<CompressionConfig>
|
||||
): Promise<CompressionConfig> {
|
||||
const db = getDbInstance();
|
||||
const insert = db.prepare(
|
||||
"INSERT OR REPLACE INTO key_value (namespace, key, value) VALUES ('compression', ?, ?)"
|
||||
);
|
||||
const tx = db.transaction(() => {
|
||||
for (const [key, value] of Object.entries(updates)) {
|
||||
insert.run(key, JSON.stringify(value));
|
||||
}
|
||||
});
|
||||
tx();
|
||||
backupDbFile("pre-write");
|
||||
return getCompressionSettings();
|
||||
}
|
||||
10
src/lib/db/migrations/034_compression_settings.sql
Normal file
10
src/lib/db/migrations/034_compression_settings.sql
Normal file
@@ -0,0 +1,10 @@
|
||||
-- 034_compression_settings.sql
|
||||
-- Insert default compression settings into key_value table (namespace='compression')
|
||||
-- Uses INSERT OR IGNORE so existing user settings are never overwritten by migration replay.
|
||||
|
||||
INSERT OR IGNORE INTO key_value (namespace, key, value) VALUES ('compression', 'enabled', 'false');
|
||||
INSERT OR IGNORE INTO key_value (namespace, key, value) VALUES ('compression', 'defaultMode', '"off"');
|
||||
INSERT OR IGNORE INTO key_value (namespace, key, value) VALUES ('compression', 'autoTriggerTokens', '0');
|
||||
INSERT OR IGNORE INTO key_value (namespace, key, value) VALUES ('compression', 'cacheMinutes', '5');
|
||||
INSERT OR IGNORE INTO key_value (namespace, key, value) VALUES ('compression', 'preserveSystemPrompt', 'true');
|
||||
INSERT OR IGNORE INTO key_value (namespace, key, value) VALUES ('compression', 'comboOverrides', '{}');
|
||||
120
tests/integration/compression-pipeline.test.ts
Normal file
120
tests/integration/compression-pipeline.test.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
import { describe, it } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import {
|
||||
selectCompressionStrategy,
|
||||
getEffectiveMode,
|
||||
applyCompression,
|
||||
} from "../../open-sse/services/compression/strategySelector.ts";
|
||||
import { applyLiteCompression } from "../../open-sse/services/compression/lite.ts";
|
||||
import {
|
||||
createCompressionStats,
|
||||
estimateCompressionTokens,
|
||||
} from "../../open-sse/services/compression/stats.ts";
|
||||
import type { CompressionConfig } from "../../open-sse/services/compression/types.ts";
|
||||
|
||||
const baseConfig: CompressionConfig = {
|
||||
enabled: true,
|
||||
defaultMode: "lite",
|
||||
autoTriggerTokens: 0,
|
||||
cacheMinutes: 5,
|
||||
preserveSystemPrompt: true,
|
||||
comboOverrides: {},
|
||||
};
|
||||
|
||||
describe("Full Compression Pipeline", () => {
|
||||
it("end-to-end: lite mode compresses whitespace and tracks stats", () => {
|
||||
const body = {
|
||||
messages: [
|
||||
{ role: "system", content: "You are helpful." },
|
||||
{ role: "system", content: "You are helpful." },
|
||||
{ role: "user", content: "hello\n\n\n\nworld" },
|
||||
],
|
||||
};
|
||||
|
||||
// Step 1: Select strategy
|
||||
const mode = selectCompressionStrategy(baseConfig, null, 100);
|
||||
assert.equal(mode, "lite");
|
||||
|
||||
// Step 2: Apply compression
|
||||
const result = applyCompression(body, mode);
|
||||
assert.equal(result.compressed, true);
|
||||
assert.ok(result.stats);
|
||||
|
||||
// Step 3: Verify stats
|
||||
assert.ok(result.stats!.originalTokens > 0);
|
||||
assert.ok(result.stats!.compressedTokens > 0);
|
||||
assert.ok(result.stats!.compressedTokens <= result.stats!.originalTokens);
|
||||
assert.ok(result.stats!.savingsPercent >= 0);
|
||||
assert.equal(result.stats!.mode, "lite");
|
||||
});
|
||||
|
||||
it("end-to-end: off mode returns unchanged body", () => {
|
||||
const body = { messages: [{ role: "user", content: "test" }] };
|
||||
const offConfig = { ...baseConfig, enabled: false };
|
||||
const mode = selectCompressionStrategy(offConfig, null, 100);
|
||||
assert.equal(mode, "off");
|
||||
|
||||
const result = applyCompression(body, mode);
|
||||
assert.equal(result.compressed, false);
|
||||
assert.equal(result.stats, null);
|
||||
});
|
||||
|
||||
it("end-to-end: combo override selects lite for specific combo", () => {
|
||||
const config: CompressionConfig = {
|
||||
...baseConfig,
|
||||
defaultMode: "off" as const,
|
||||
comboOverrides: { "my-combo": "lite" as const },
|
||||
};
|
||||
const body = { messages: [{ role: "user", content: "test\n\n\n\nmessage" }] };
|
||||
|
||||
const mode = selectCompressionStrategy(config, "my-combo", 100);
|
||||
assert.equal(mode, "lite");
|
||||
|
||||
const result = applyCompression(body, mode);
|
||||
assert.equal(result.compressed, true);
|
||||
});
|
||||
|
||||
it("end-to-end: auto-trigger activates compression", () => {
|
||||
const config: CompressionConfig = {
|
||||
...baseConfig,
|
||||
defaultMode: "off" as const,
|
||||
autoTriggerTokens: 50,
|
||||
};
|
||||
const body = { messages: [{ role: "user", content: "x".repeat(1000) }] };
|
||||
|
||||
// Below threshold → off
|
||||
const mode1 = getEffectiveMode(config, null, 10);
|
||||
assert.equal(mode1, "off");
|
||||
|
||||
// Above threshold → lite
|
||||
const mode2 = getEffectiveMode(config, null, 100);
|
||||
assert.equal(mode2, "lite");
|
||||
});
|
||||
|
||||
it("lite compression + stats pipeline works together", () => {
|
||||
const body = {
|
||||
messages: [
|
||||
{ role: "system", content: "Be helpful." },
|
||||
{ role: "system", content: "Be helpful." },
|
||||
{ role: "user", content: "hello\n\n\n\nworld" },
|
||||
{ role: "user", content: "hello\n\n\n\nworld" },
|
||||
],
|
||||
};
|
||||
|
||||
const result = applyLiteCompression(body);
|
||||
assert.equal(result.compressed, true);
|
||||
assert.ok(result.stats);
|
||||
assert.ok(result.stats.savingsPercent > 0);
|
||||
assert.ok(result.stats.techniquesUsed.length >= 1);
|
||||
});
|
||||
|
||||
it("token estimation is consistent", () => {
|
||||
const text = "hello world this is a test";
|
||||
const tokens = estimateCompressionTokens(text);
|
||||
assert.equal(tokens, Math.ceil(text.length / 4));
|
||||
|
||||
const obj = { messages: [{ role: "user", content: text }] };
|
||||
const objTokens = estimateCompressionTokens(obj);
|
||||
assert.equal(objTokens, Math.ceil(JSON.stringify(obj).length / 4));
|
||||
});
|
||||
});
|
||||
@@ -8,12 +8,10 @@ const {
|
||||
CLI_COMPAT_TOGGLE_IDS,
|
||||
IMPLEMENTED_CLI_FINGERPRINT_PROVIDER_IDS,
|
||||
normalizeCliCompatProviderId,
|
||||
} =
|
||||
await import("../../src/shared/constants/cliCompatProviders.ts");
|
||||
} = await import("../../src/shared/constants/cliCompatProviders.ts");
|
||||
const { CLI_TOOL_IDS } = await import("../../src/shared/services/cliRuntime.ts");
|
||||
const { applyFingerprint, isCliCompatEnabled, setCliCompatProviders } = await import(
|
||||
"../../open-sse/config/cliFingerprints.ts"
|
||||
);
|
||||
const { applyFingerprint, isCliCompatEnabled, setCliCompatProviders } =
|
||||
await import("../../open-sse/config/cliFingerprints.ts");
|
||||
|
||||
test("Amp CLI is registered as a guide-based CLI tool with shorthand mapping guidance", () => {
|
||||
const amp = CLI_TOOLS.amp;
|
||||
|
||||
@@ -71,10 +71,10 @@ async function applyStreamReadiness(response: Response): Promise<Response> {
|
||||
}
|
||||
|
||||
function errorResponse(status: number, message: string): Response {
|
||||
return new Response(
|
||||
JSON.stringify({ error: { message, type: "upstream_error" } }),
|
||||
{ status, headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
return new Response(JSON.stringify({ error: { message, type: "upstream_error" } }), {
|
||||
status,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
|
||||
test("combo falls back when first model returns HTTP 200 zombie SSE stream", async () => {
|
||||
@@ -113,10 +113,7 @@ test("combo falls back when first model returns HTTP 200 zombie SSE stream", asy
|
||||
assert.deepEqual(calls, ["glm/zombie-model", "openai/gpt-5.4-mini"]);
|
||||
assert.ok(
|
||||
log.entries.some(
|
||||
(e) =>
|
||||
e.level === "warn" &&
|
||||
e.tag === "COMBO" &&
|
||||
String(e.msg).includes("glm/zombie-model")
|
||||
(e) => e.level === "warn" && e.tag === "COMBO" && String(e.msg).includes("glm/zombie-model")
|
||||
),
|
||||
"combo should log warning for the failed model"
|
||||
);
|
||||
@@ -190,11 +187,7 @@ test("combo retries 504 on same model before falling through (transient retry)",
|
||||
|
||||
assert.equal(result.ok, true, "combo should succeed via fallback after retries");
|
||||
const zombieCalls = calls.filter((c) => c === "glm/zombie");
|
||||
assert.equal(
|
||||
zombieCalls.length,
|
||||
2,
|
||||
"combo should retry zombie once before falling through"
|
||||
);
|
||||
assert.equal(zombieCalls.length, 2, "combo should retry zombie once before falling through");
|
||||
assert.ok(calls.includes("openai/gpt-5.4-mini"), "combo should reach fallback model");
|
||||
});
|
||||
|
||||
|
||||
113
tests/unit/compression/db.test.ts
Normal file
113
tests/unit/compression/db.test.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
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";
|
||||
|
||||
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-compression-db-"));
|
||||
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("getCompressionSettings", () => {
|
||||
it("returns default settings structure", async () => {
|
||||
const settings = await getCompressionSettings();
|
||||
assert.equal(typeof settings.enabled, "boolean");
|
||||
assert.equal(typeof settings.defaultMode, "string");
|
||||
assert.equal(typeof settings.autoTriggerTokens, "number");
|
||||
assert.equal(typeof settings.cacheMinutes, "number");
|
||||
assert.equal(typeof settings.preserveSystemPrompt, "boolean");
|
||||
assert.equal(typeof settings.comboOverrides, "object");
|
||||
});
|
||||
|
||||
it("has correct default values", async () => {
|
||||
const settings = await getCompressionSettings();
|
||||
assert.equal(settings.enabled, false);
|
||||
assert.equal(settings.defaultMode, "off");
|
||||
assert.equal(settings.autoTriggerTokens, 0);
|
||||
assert.equal(settings.cacheMinutes, 5);
|
||||
assert.equal(settings.preserveSystemPrompt, true);
|
||||
assert.deepEqual(settings.comboOverrides, {});
|
||||
});
|
||||
});
|
||||
|
||||
describe("updateCompressionSettings", () => {
|
||||
it("updates enabled flag", async () => {
|
||||
await updateCompressionSettings({ enabled: true } as any);
|
||||
const settings = await getCompressionSettings();
|
||||
assert.equal(settings.enabled, true);
|
||||
// Reset
|
||||
await updateCompressionSettings({ enabled: false } as any);
|
||||
});
|
||||
|
||||
it("updates defaultMode", async () => {
|
||||
await updateCompressionSettings({ defaultMode: "lite" } as any);
|
||||
const settings = await getCompressionSettings();
|
||||
assert.equal(settings.defaultMode, "lite");
|
||||
// Reset
|
||||
await updateCompressionSettings({ defaultMode: "off" } as any);
|
||||
});
|
||||
|
||||
it("updates autoTriggerTokens", async () => {
|
||||
await updateCompressionSettings({ autoTriggerTokens: 5000 } as any);
|
||||
const settings = await getCompressionSettings();
|
||||
assert.equal(settings.autoTriggerTokens, 5000);
|
||||
// Reset
|
||||
await updateCompressionSettings({ autoTriggerTokens: 0 } as any);
|
||||
});
|
||||
|
||||
it("updates multiple settings at once", async () => {
|
||||
await updateCompressionSettings({
|
||||
enabled: true,
|
||||
defaultMode: "lite",
|
||||
autoTriggerTokens: 1000,
|
||||
cacheMinutes: 10,
|
||||
} as any);
|
||||
const settings = await getCompressionSettings();
|
||||
assert.equal(settings.enabled, true);
|
||||
assert.equal(settings.defaultMode, "lite");
|
||||
assert.equal(settings.autoTriggerTokens, 1000);
|
||||
assert.equal(settings.cacheMinutes, 10);
|
||||
// Reset all
|
||||
await updateCompressionSettings({
|
||||
enabled: false,
|
||||
defaultMode: "off",
|
||||
autoTriggerTokens: 0,
|
||||
cacheMinutes: 5,
|
||||
} as any);
|
||||
});
|
||||
|
||||
it("preserves unmodified settings", async () => {
|
||||
const before = await getCompressionSettings();
|
||||
await updateCompressionSettings({ enabled: true } as any);
|
||||
const after = await getCompressionSettings();
|
||||
assert.equal(after.enabled, true);
|
||||
assert.equal(after.defaultMode, before.defaultMode);
|
||||
assert.equal(after.cacheMinutes, before.cacheMinutes);
|
||||
// Reset
|
||||
await updateCompressionSettings({ enabled: false } as any);
|
||||
});
|
||||
});
|
||||
209
tests/unit/compression/lite.test.ts
Normal file
209
tests/unit/compression/lite.test.ts
Normal file
@@ -0,0 +1,209 @@
|
||||
import { describe, it } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import {
|
||||
applyLiteCompression,
|
||||
collapseWhitespace,
|
||||
dedupSystemPrompt,
|
||||
compressToolResults,
|
||||
removeRedundantContent,
|
||||
replaceImageUrls,
|
||||
} from "../../../open-sse/services/compression/lite.ts";
|
||||
|
||||
describe("collapseWhitespace", () => {
|
||||
it("collapses 3+ newlines to 2", () => {
|
||||
const body = { messages: [{ role: "user", content: "hello\n\n\n\nworld" }] };
|
||||
const result = collapseWhitespace(body);
|
||||
assert.equal(result.applied, true);
|
||||
assert.equal(result.body.messages![0].content as string, "hello\n\nworld");
|
||||
});
|
||||
|
||||
it("does not modify already-normal whitespace", () => {
|
||||
const body = { messages: [{ role: "user", content: "hello\n\nworld" }] };
|
||||
const result = collapseWhitespace(body);
|
||||
assert.equal(result.applied, false);
|
||||
});
|
||||
|
||||
it("trims trailing spaces", () => {
|
||||
const body = { messages: [{ role: "user", content: "hello " }] };
|
||||
const result = collapseWhitespace(body);
|
||||
assert.equal(result.applied, true);
|
||||
assert.equal(result.body.messages![0].content as string, "hello");
|
||||
});
|
||||
|
||||
it("returns unchanged when no messages", () => {
|
||||
const body = {};
|
||||
const result = collapseWhitespace(body);
|
||||
assert.equal(result.applied, false);
|
||||
});
|
||||
|
||||
it("skips non-string content", () => {
|
||||
const body = { messages: [{ role: "user", content: [{ type: "text", text: "hello" }] }] };
|
||||
const result = collapseWhitespace(body);
|
||||
assert.equal(result.applied, false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("dedupSystemPrompt", () => {
|
||||
it("removes duplicate system prompts", () => {
|
||||
const body = {
|
||||
messages: [
|
||||
{ role: "system", content: "You are helpful." },
|
||||
{ role: "system", content: "You are helpful." },
|
||||
{ role: "user", content: "hi" },
|
||||
],
|
||||
};
|
||||
const result = dedupSystemPrompt(body);
|
||||
assert.equal(result.applied, true);
|
||||
assert.equal(result.body.messages!.length, 2);
|
||||
});
|
||||
|
||||
it("keeps different system prompts", () => {
|
||||
const body = {
|
||||
messages: [
|
||||
{ role: "system", content: "You are helpful." },
|
||||
{ role: "system", content: "Be concise." },
|
||||
],
|
||||
};
|
||||
const result = dedupSystemPrompt(body);
|
||||
assert.equal(result.applied, false);
|
||||
assert.equal(result.body.messages!.length, 2);
|
||||
});
|
||||
|
||||
it("returns unchanged when no messages", () => {
|
||||
const result = dedupSystemPrompt({});
|
||||
assert.equal(result.applied, false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("compressToolResults", () => {
|
||||
it("truncates long tool results", () => {
|
||||
const longContent = "x".repeat(3000);
|
||||
const body = { messages: [{ role: "tool", content: longContent }] };
|
||||
const result = compressToolResults(body);
|
||||
assert.equal(result.applied, true);
|
||||
const content = result.body.messages![0].content as string;
|
||||
assert.ok(content.length < 3000);
|
||||
assert.ok(content.includes("[truncated]"));
|
||||
});
|
||||
|
||||
it("keeps short tool results unchanged", () => {
|
||||
const body = { messages: [{ role: "tool", content: "short result" }] };
|
||||
const result = compressToolResults(body);
|
||||
assert.equal(result.applied, false);
|
||||
});
|
||||
|
||||
it("skips non-tool messages", () => {
|
||||
const body = { messages: [{ role: "user", content: "x".repeat(3000) }] };
|
||||
const result = compressToolResults(body);
|
||||
assert.equal(result.applied, false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("removeRedundantContent", () => {
|
||||
it("removes consecutive duplicate messages", () => {
|
||||
const body = {
|
||||
messages: [
|
||||
{ role: "user", content: "hello" },
|
||||
{ role: "user", content: "hello" },
|
||||
],
|
||||
};
|
||||
const result = removeRedundantContent(body);
|
||||
assert.equal(result.applied, true);
|
||||
assert.equal(result.body.messages!.length, 1);
|
||||
});
|
||||
|
||||
it("keeps non-duplicate messages", () => {
|
||||
const body = {
|
||||
messages: [
|
||||
{ role: "user", content: "hello" },
|
||||
{ role: "user", content: "world" },
|
||||
],
|
||||
};
|
||||
const result = removeRedundantContent(body);
|
||||
assert.equal(result.applied, false);
|
||||
assert.equal(result.body.messages!.length, 2);
|
||||
});
|
||||
|
||||
it("only removes same-role consecutive duplicates", () => {
|
||||
const body = {
|
||||
messages: [
|
||||
{ role: "system", content: "hello" },
|
||||
{ role: "user", content: "hello" },
|
||||
],
|
||||
};
|
||||
const result = removeRedundantContent(body);
|
||||
assert.equal(result.applied, false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("replaceImageUrls", () => {
|
||||
it("replaces base64 images for non-vision models", () => {
|
||||
const body = {
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: [{ type: "image_url", image_url: { url: "data:image/png;base64,iVBOR" } }],
|
||||
},
|
||||
],
|
||||
};
|
||||
const result = replaceImageUrls(body, "gpt-3.5-turbo");
|
||||
assert.equal(result.applied, true);
|
||||
const content = result.body.messages![0].content as Array<Record<string, unknown>>;
|
||||
assert.equal(content[0].type, "text");
|
||||
assert.ok((content[0].text as string).includes("[image:"));
|
||||
});
|
||||
|
||||
it("keeps images for vision models", () => {
|
||||
const body = {
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: [{ type: "image_url", image_url: { url: "data:image/png;base64,iVBOR" } }],
|
||||
},
|
||||
],
|
||||
};
|
||||
const result = replaceImageUrls(body, "gpt-4o");
|
||||
assert.equal(result.applied, false);
|
||||
});
|
||||
|
||||
it("skips non-image content", () => {
|
||||
const body = {
|
||||
messages: [{ role: "user", content: "just text" }],
|
||||
};
|
||||
const result = replaceImageUrls(body, "gpt-3.5-turbo");
|
||||
assert.equal(result.applied, false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("applyLiteCompression", () => {
|
||||
it("applies all techniques that match", () => {
|
||||
const body = {
|
||||
messages: [
|
||||
{ role: "system", content: "You are helpful." },
|
||||
{ role: "system", content: "You are helpful." },
|
||||
{ role: "user", content: "hello\n\n\n\nworld" },
|
||||
{ role: "user", content: "hello\n\n\n\nworld" },
|
||||
],
|
||||
};
|
||||
const result = applyLiteCompression(body);
|
||||
assert.equal(result.compressed, true);
|
||||
assert.ok(result.stats);
|
||||
assert.ok(result.stats.techniquesUsed.length >= 2);
|
||||
assert.ok(result.stats.savingsPercent > 0);
|
||||
});
|
||||
|
||||
it("returns no compression for clean input", () => {
|
||||
const body = {
|
||||
messages: [{ role: "user", content: "clean message" }],
|
||||
};
|
||||
const result = applyLiteCompression(body);
|
||||
assert.equal(result.compressed, false);
|
||||
assert.equal(result.stats, null);
|
||||
});
|
||||
|
||||
it("handles empty messages array", () => {
|
||||
const body = { messages: [] };
|
||||
const result = applyLiteCompression(body);
|
||||
assert.equal(result.compressed, false);
|
||||
});
|
||||
});
|
||||
92
tests/unit/compression/stats.test.ts
Normal file
92
tests/unit/compression/stats.test.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import { describe, it } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import {
|
||||
estimateCompressionTokens,
|
||||
createCompressionStats,
|
||||
trackCompressionStats,
|
||||
} from "../../../open-sse/services/compression/stats.ts";
|
||||
|
||||
describe("estimateCompressionTokens", () => {
|
||||
it("returns 0 for null", () => {
|
||||
assert.equal(estimateCompressionTokens(null), 0);
|
||||
});
|
||||
|
||||
it("returns 0 for undefined", () => {
|
||||
assert.equal(estimateCompressionTokens(undefined), 0);
|
||||
});
|
||||
|
||||
it("returns 0 for empty string", () => {
|
||||
assert.equal(estimateCompressionTokens(""), 0);
|
||||
});
|
||||
|
||||
it("estimates tokens from text (chars/4)", () => {
|
||||
assert.equal(estimateCompressionTokens("hello world"), 3);
|
||||
});
|
||||
|
||||
it("estimates tokens from object", () => {
|
||||
const tokens = estimateCompressionTokens({ messages: [{ role: "user", content: "test" }] });
|
||||
assert.ok(tokens > 0);
|
||||
});
|
||||
|
||||
it("handles long strings", () => {
|
||||
const tokens = estimateCompressionTokens("x".repeat(400));
|
||||
assert.equal(tokens, 100);
|
||||
});
|
||||
});
|
||||
|
||||
describe("createCompressionStats", () => {
|
||||
it("calculates savings correctly", () => {
|
||||
const original = { messages: [{ role: "user", content: "x".repeat(100) }] };
|
||||
const compressed = { messages: [{ role: "user", content: "x".repeat(80) }] };
|
||||
const origTokens = Math.ceil(JSON.stringify(original).length / 4);
|
||||
const compTokens = Math.ceil(JSON.stringify(compressed).length / 4);
|
||||
const expectedSavings = Math.round(((origTokens - compTokens) / origTokens) * 10000) / 100;
|
||||
const stats = createCompressionStats(original, compressed, "lite", ["whitespace"]);
|
||||
assert.equal(stats.originalTokens, origTokens);
|
||||
assert.equal(stats.compressedTokens, compTokens);
|
||||
assert.equal(stats.savingsPercent, expectedSavings);
|
||||
assert.deepEqual(stats.techniquesUsed, ["whitespace"]);
|
||||
assert.equal(stats.mode, "lite");
|
||||
assert.ok(stats.timestamp > 0);
|
||||
});
|
||||
|
||||
it("handles zero original tokens", () => {
|
||||
const original = {};
|
||||
const compressed = {};
|
||||
const stats = createCompressionStats(original, compressed, "off", []);
|
||||
assert.equal(stats.savingsPercent, 0);
|
||||
});
|
||||
|
||||
it("rounds savings to 2 decimal places", () => {
|
||||
const original = { messages: [{ role: "user", content: "x".repeat(97) }] };
|
||||
const compressed = { messages: [{ role: "user", content: "x".repeat(80) }] };
|
||||
const stats = createCompressionStats(original, compressed, "lite", ["test"]);
|
||||
assert.ok(Number.isFinite(stats.savingsPercent));
|
||||
});
|
||||
});
|
||||
|
||||
describe("trackCompressionStats", () => {
|
||||
it("does not throw for zero tokens", () => {
|
||||
const stats = {
|
||||
originalTokens: 0,
|
||||
compressedTokens: 0,
|
||||
savingsPercent: 0,
|
||||
techniquesUsed: [],
|
||||
mode: "off" as const,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
assert.doesNotThrow(() => trackCompressionStats(stats));
|
||||
});
|
||||
|
||||
it("logs compression stats", () => {
|
||||
const stats = {
|
||||
originalTokens: 100,
|
||||
compressedTokens: 80,
|
||||
savingsPercent: 20,
|
||||
techniquesUsed: ["whitespace"],
|
||||
mode: "lite" as const,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
assert.doesNotThrow(() => trackCompressionStats(stats));
|
||||
});
|
||||
});
|
||||
125
tests/unit/compression/strategySelector.test.ts
Normal file
125
tests/unit/compression/strategySelector.test.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
import { describe, it } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import {
|
||||
selectCompressionStrategy,
|
||||
getEffectiveMode,
|
||||
applyCompression,
|
||||
checkComboOverride,
|
||||
shouldAutoTrigger,
|
||||
} from "../../../open-sse/services/compression/strategySelector.ts";
|
||||
import type { CompressionConfig } from "../../../open-sse/services/compression/types.ts";
|
||||
|
||||
const baseConfig: CompressionConfig = {
|
||||
enabled: true,
|
||||
defaultMode: "lite",
|
||||
autoTriggerTokens: 0,
|
||||
cacheMinutes: 5,
|
||||
preserveSystemPrompt: true,
|
||||
comboOverrides: {},
|
||||
};
|
||||
|
||||
describe("checkComboOverride", () => {
|
||||
it("returns null when comboId is null", () => {
|
||||
assert.equal(checkComboOverride(baseConfig, null), null);
|
||||
});
|
||||
|
||||
it("returns null when comboOverrides is empty", () => {
|
||||
assert.equal(checkComboOverride(baseConfig, "my-combo"), null);
|
||||
});
|
||||
|
||||
it("returns mode when combo override exists", () => {
|
||||
const config = { ...baseConfig, comboOverrides: { "my-combo": "off" as const } };
|
||||
assert.equal(checkComboOverride(config, "my-combo"), "off");
|
||||
});
|
||||
|
||||
it("returns null for non-existent combo", () => {
|
||||
const config = { ...baseConfig, comboOverrides: { "other-combo": "lite" as const } };
|
||||
assert.equal(checkComboOverride(config, "my-combo"), null);
|
||||
});
|
||||
});
|
||||
|
||||
describe("shouldAutoTrigger", () => {
|
||||
it("returns false when autoTriggerTokens is 0", () => {
|
||||
assert.equal(shouldAutoTrigger(baseConfig, 5000), false);
|
||||
});
|
||||
|
||||
it("returns false when tokens below threshold", () => {
|
||||
const config = { ...baseConfig, autoTriggerTokens: 1000 };
|
||||
assert.equal(shouldAutoTrigger(config, 500), false);
|
||||
});
|
||||
|
||||
it("returns true when tokens at threshold", () => {
|
||||
const config = { ...baseConfig, autoTriggerTokens: 1000 };
|
||||
assert.equal(shouldAutoTrigger(config, 1000), true);
|
||||
});
|
||||
|
||||
it("returns true when tokens above threshold", () => {
|
||||
const config = { ...baseConfig, autoTriggerTokens: 1000 };
|
||||
assert.equal(shouldAutoTrigger(config, 1500), true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getEffectiveMode", () => {
|
||||
it("returns off when not enabled", () => {
|
||||
const config = { ...baseConfig, enabled: false };
|
||||
assert.equal(getEffectiveMode(config, null, 100), "off");
|
||||
});
|
||||
|
||||
it("returns default mode when no overrides", () => {
|
||||
assert.equal(getEffectiveMode(baseConfig, null, 100), "lite");
|
||||
});
|
||||
|
||||
it("returns combo override mode when present", () => {
|
||||
const config = {
|
||||
...baseConfig,
|
||||
defaultMode: "off" as const,
|
||||
comboOverrides: { "my-combo": "lite" as const },
|
||||
};
|
||||
assert.equal(getEffectiveMode(config, "my-combo", 100), "lite");
|
||||
});
|
||||
|
||||
it("returns lite when auto-trigger threshold reached", () => {
|
||||
const config = { ...baseConfig, defaultMode: "off" as const, autoTriggerTokens: 1000 };
|
||||
assert.equal(getEffectiveMode(config, null, 1500), "lite");
|
||||
});
|
||||
|
||||
it("combo override takes precedence over auto-trigger", () => {
|
||||
const config = {
|
||||
...baseConfig,
|
||||
defaultMode: "off" as const,
|
||||
autoTriggerTokens: 100,
|
||||
comboOverrides: { "my-combo": "off" as const },
|
||||
};
|
||||
assert.equal(getEffectiveMode(config, "my-combo", 500), "off");
|
||||
});
|
||||
});
|
||||
|
||||
describe("selectCompressionStrategy", () => {
|
||||
it("returns effective mode", () => {
|
||||
assert.equal(selectCompressionStrategy(baseConfig, null, 100), "lite");
|
||||
});
|
||||
});
|
||||
|
||||
describe("applyCompression", () => {
|
||||
it("returns unchanged body for off mode", () => {
|
||||
const body = { messages: [{ role: "user", content: "test" }] };
|
||||
const result = applyCompression(body, "off");
|
||||
assert.equal(result.compressed, false);
|
||||
assert.equal(result.stats, null);
|
||||
assert.deepEqual(result.body, body);
|
||||
});
|
||||
|
||||
it("applies lite compression for lite mode", () => {
|
||||
const body = { messages: [{ role: "user", content: "test\n\n\n\nmessage" }] };
|
||||
const result = applyCompression(body, "lite");
|
||||
assert.equal(result.compressed, true);
|
||||
assert.ok(result.stats);
|
||||
assert.equal(result.stats.mode, "lite");
|
||||
});
|
||||
|
||||
it("returns unchanged body for standard mode (Phase 2)", () => {
|
||||
const body = { messages: [{ role: "user", content: "test" }] };
|
||||
const result = applyCompression(body, "standard");
|
||||
assert.equal(result.compressed, false);
|
||||
});
|
||||
});
|
||||
@@ -48,7 +48,10 @@ test("provider header profiles expose dedicated refresh, qwen, qoder, kiro and c
|
||||
|
||||
const qwenHeaders = getQwenOauthHeaders();
|
||||
assert.equal(qwenHeaders["User-Agent"], getQwenCliUserAgent());
|
||||
assert.equal(qwenHeaders["User-Agent"], `QwenCode/${QWEN_CLI_VERSION} (${process.platform}; ${process.arch})`);
|
||||
assert.equal(
|
||||
qwenHeaders["User-Agent"],
|
||||
`QwenCode/${QWEN_CLI_VERSION} (${process.platform}; ${process.arch})`
|
||||
);
|
||||
assert.notEqual(qwenHeaders["User-Agent"], "QwenCode/0.15.3 (linux; x64)");
|
||||
assert.equal(qwenHeaders["X-Dashscope-UserAgent"], getQwenCliUserAgent());
|
||||
assert.equal(qwenHeaders["X-Stainless-Package-Version"], "5.11.0");
|
||||
|
||||
@@ -100,7 +100,10 @@ test("ensureStreamReadiness preserves buffered chunks when stream starts", async
|
||||
|
||||
test("ensureStreamReadiness returns 504 when no useful content arrives before timeout", async () => {
|
||||
const response = new Response(
|
||||
streamFromChunks([": keepalive\n\n", `data: ${JSON.stringify({ type: "response.created" })}\n\n`], 20),
|
||||
streamFromChunks(
|
||||
[": keepalive\n\n", `data: ${JSON.stringify({ type: "response.created" })}\n\n`],
|
||||
20
|
||||
),
|
||||
{ status: 200, headers: { "Content-Type": "text/event-stream" } }
|
||||
);
|
||||
|
||||
|
||||
@@ -421,7 +421,9 @@ test("usage service manual Antigravity refresh bypasses usage TTL caches", async
|
||||
globalThis.fetch = async (url) => {
|
||||
const urlStr = String(url);
|
||||
if (urlStr.includes("loadCodeAssist")) {
|
||||
return new Response(JSON.stringify({ cloudaicompanionProject: "ag-project" }), { status: 200 });
|
||||
return new Response(JSON.stringify({ cloudaicompanionProject: "ag-project" }), {
|
||||
status: 200,
|
||||
});
|
||||
}
|
||||
|
||||
if (urlStr.includes("streamGenerateContent")) {
|
||||
|
||||
Reference in New Issue
Block a user