Merge pull request #246 from diegosouzapw/feat/features-07-09-04-06-electron-release

feat: features 07/09/04/06 — Codex effort clamp, OpenRouter catalog cache, quota preflight & session monitor
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-03-08 17:38:06 -03:00
committed by GitHub
7 changed files with 482 additions and 2 deletions

View File

@@ -2,6 +2,39 @@ import { BaseExecutor } from "./base.ts";
import { CODEX_DEFAULT_INSTRUCTIONS } from "../config/codexInstructions.ts";
import { PROVIDERS } from "../config/constants.ts";
// Ordered list of effort levels from lowest to highest
const EFFORT_ORDER = ["none", "low", "medium", "high", "xhigh"] as const;
type EffortLevel = (typeof EFFORT_ORDER)[number];
/**
* Maximum reasoning effort allowed per Codex model.
* Models not listed here default to "xhigh" (unrestricted).
* Update this table when Codex releases new models with different caps.
*/
const MAX_EFFORT_BY_MODEL: Record<string, EffortLevel> = {
"gpt-5.3-codex": "xhigh",
"gpt-5.2-codex": "xhigh",
"gpt-5.1-codex-max": "xhigh",
"gpt-5-mini": "high",
"gpt-5.1-mini": "high",
"gpt-4.1-mini": "high",
};
/**
* Clamp reasoning effort to the model's maximum allowed level.
* Returns the original value if within limits, or the cap if it exceeds it.
*/
function clampEffort(model: string, requested: string): string {
const max: EffortLevel = MAX_EFFORT_BY_MODEL[model] ?? "xhigh";
const reqIdx = EFFORT_ORDER.indexOf(requested as EffortLevel);
const maxIdx = EFFORT_ORDER.indexOf(max);
if (reqIdx > maxIdx) {
console.debug(`[Codex] clampEffort: "${requested}" → "${max}" (model: ${model})`);
return max;
}
return requested;
}
/**
* Codex Executor - handles OpenAI Codex API (Responses API format)
* Automatically injects default instructions if missing.
@@ -47,20 +80,28 @@ export class CodexExecutor extends BaseExecutor {
// Extract thinking level from model name suffix
// e.g., gpt-5.3-codex-high → high, gpt-5.3-codex → medium (default)
const effortLevels = ["none", "low", "medium", "high", "xhigh"];
let modelEffort = null;
let modelEffort: string | null = null;
// Track the clean model name (suffix stripped) for clamp lookup
let cleanModel = model;
for (const level of effortLevels) {
if (model.endsWith(`-${level}`)) {
modelEffort = level;
// Strip suffix from model name for actual API call
body.model = body.model.replace(`-${level}`, "");
cleanModel = body.model;
break;
}
}
// Priority: explicit reasoning.effort > reasoning_effort param > model suffix > default (medium)
if (!body.reasoning) {
const effort = body.reasoning_effort || modelEffort || "medium";
const rawEffort = body.reasoning_effort || modelEffort || "medium";
// Clamp effort to the model's maximum allowed level (feature-07)
const effort = clampEffort(cleanModel, rawEffort);
body.reasoning = { effort };
} else if (body.reasoning.effort) {
// Also clamp if reasoning object was provided directly
body.reasoning.effort = clampEffort(cleanModel, body.reasoning.effort);
}
delete body.reasoning_effort;

View File

@@ -0,0 +1,126 @@
/**
* quotaMonitor.ts — Feature 06
* Monitoramento de Quota em Sessão Ativa
*
* Toggle: providerSpecificData.quotaMonitorEnabled (default: false)
* Polling adaptativo: NORMAL (60s) → CRITICAL (15s) → EXHAUSTED
* timer.unref() garante que o processo pode fechar normalmente.
* Alertas deduplicados por sessão (janela de 5min).
*/
import { registerQuotaFetcher, type QuotaFetcher } from "./quotaPreflight.ts";
export { registerQuotaFetcher };
export type { QuotaFetcher };
const NORMAL_INTERVAL_MS = 60_000;
const CRITICAL_INTERVAL_MS = 15_000;
const WARN_THRESHOLD = 0.8;
const EXHAUSTION_THRESHOLD = 0.95;
const ALERT_SUPPRESS_WINDOW_MS = 5 * 60_000;
interface MonitorState {
timer: ReturnType<typeof setTimeout> | null;
stopped: boolean;
provider: string;
accountId: string;
}
const activeMonitors = new Map<string, MonitorState>();
const alertSuppression = new Map<string, number>();
// Registry mirror from quotaPreflight (same Map reference via re-export)
const quotaFetcherRegistry = new Map<string, QuotaFetcher>();
export function registerMonitorFetcher(provider: string, fetcher: QuotaFetcher): void {
quotaFetcherRegistry.set(provider, fetcher);
registerQuotaFetcher(provider, fetcher);
}
export function isQuotaMonitorEnabled(connection: Record<string, unknown>): boolean {
const psd = connection?.providerSpecificData as Record<string, unknown> | undefined;
return psd?.quotaMonitorEnabled === true;
}
function suppressedAlert(
sessionId: string,
provider: string,
accountId: string,
percentUsed: number
): void {
const key = `${sessionId}:${provider}:${accountId}`;
const last = alertSuppression.get(key) ?? 0;
if (Date.now() - last < ALERT_SUPPRESS_WINDOW_MS) return;
alertSuppression.set(key, Date.now());
console.warn(
`[QuotaMonitor] session=${sessionId} ${provider}/${accountId}: ${(percentUsed * 100).toFixed(1)}% quota used`
);
}
function scheduleNextPoll(sessionId: string, intervalMs: number): void {
const state = activeMonitors.get(sessionId);
if (!state || state.stopped) return;
const { provider, accountId } = state;
const timer = setTimeout(async () => {
const current = activeMonitors.get(sessionId);
if (!current || current.stopped) return;
try {
const fetcher = quotaFetcherRegistry.get(provider);
if (!fetcher) {
scheduleNextPoll(sessionId, NORMAL_INTERVAL_MS);
return;
}
const quota = await fetcher(accountId);
const percentUsed = quota?.percentUsed ?? 0;
if (percentUsed >= EXHAUSTION_THRESHOLD) {
suppressedAlert(sessionId, provider, accountId, percentUsed);
console.info(
`[QuotaMonitor] session=${sessionId}: marking ${accountId} for next-session cooldown`
);
scheduleNextPoll(sessionId, CRITICAL_INTERVAL_MS);
} else if (percentUsed >= WARN_THRESHOLD) {
suppressedAlert(sessionId, provider, accountId, percentUsed);
scheduleNextPoll(sessionId, CRITICAL_INTERVAL_MS);
} else {
scheduleNextPoll(sessionId, NORMAL_INTERVAL_MS);
}
} catch {
scheduleNextPoll(sessionId, NORMAL_INTERVAL_MS);
}
}, intervalMs);
if (typeof timer.unref === "function") timer.unref();
state.timer = timer;
}
export function startQuotaMonitor(
sessionId: string,
provider: string,
accountId: string,
connection: Record<string, unknown>
): void {
if (!isQuotaMonitorEnabled(connection)) return;
if (activeMonitors.has(sessionId)) return;
activeMonitors.set(sessionId, { timer: null, stopped: false, provider, accountId });
scheduleNextPoll(sessionId, NORMAL_INTERVAL_MS);
}
export function stopQuotaMonitor(sessionId: string): void {
const state = activeMonitors.get(sessionId);
if (!state) return;
state.stopped = true;
if (state.timer) {
clearTimeout(state.timer);
state.timer = null;
}
activeMonitors.delete(sessionId);
for (const key of alertSuppression.keys()) {
if (key.startsWith(`${sessionId}:`)) alertSuppression.delete(key);
}
}
export function getActiveMonitorCount(): number {
return activeMonitors.size;
}

View File

@@ -0,0 +1,79 @@
/**
* quotaPreflight.ts — Feature 04
* Quota Preflight & Troca Proativa de Conta
*
* Toggle: providerSpecificData.quotaPreflightEnabled (default: false)
* Providers register quota fetchers via registerQuotaFetcher().
* Graceful degradation when no fetcher registered.
*/
export interface PreflightQuotaResult {
proceed: boolean;
reason?: string;
quotaPercent?: number;
}
export interface QuotaInfo {
used: number;
total: number;
percentUsed: number;
}
export type QuotaFetcher = (connectionId: string) => Promise<QuotaInfo | null>;
const EXHAUSTION_THRESHOLD = 0.95;
const WARN_THRESHOLD = 0.8;
const quotaFetcherRegistry = new Map<string, QuotaFetcher>();
export function registerQuotaFetcher(provider: string, fetcher: QuotaFetcher): void {
quotaFetcherRegistry.set(provider, fetcher);
}
export function isQuotaPreflightEnabled(connection: Record<string, unknown>): boolean {
const psd = connection?.providerSpecificData as Record<string, unknown> | undefined;
return psd?.quotaPreflightEnabled === true;
}
export async function preflightQuota(
provider: string,
connectionId: string,
connection: Record<string, unknown>
): Promise<PreflightQuotaResult> {
if (!isQuotaPreflightEnabled(connection)) {
return { proceed: true };
}
const fetcher = quotaFetcherRegistry.get(provider);
if (!fetcher) {
return { proceed: true };
}
let quota: QuotaInfo | null = null;
try {
quota = await fetcher(connectionId);
} catch {
return { proceed: true };
}
if (!quota) {
return { proceed: true };
}
const { percentUsed } = quota;
if (percentUsed >= EXHAUSTION_THRESHOLD) {
console.info(
`[QuotaPreflight] ${provider}/${connectionId}: ${(percentUsed * 100).toFixed(1)}% used — switching`
);
return { proceed: false, reason: "quota_exhausted", quotaPercent: percentUsed };
}
if (percentUsed >= WARN_THRESHOLD) {
console.warn(
`[QuotaPreflight] ${provider}/${connectionId}: ${(percentUsed * 100).toFixed(1)}% used — approaching limit`
);
}
return { proceed: true, quotaPercent: percentUsed };
}

View File

@@ -0,0 +1,48 @@
/**
* GET /api/models/openrouter-catalog
* Feature 09 — Retorna catálogo OpenRouter com cache persistente.
*
* Query params:
* ?refresh=true — Force-refresh, ignores TTL
*/
import { NextRequest, NextResponse } from "next/server";
import { isAuthenticated } from "@/shared/utils/apiAuth";
import { getOpenRouterCatalog, refreshOpenRouterCatalog } from "@/lib/catalog/openrouterCatalog";
export async function GET(req: NextRequest) {
// Require authentication (dashboard/API key)
if (!(await isAuthenticated(req))) {
return NextResponse.json(
{ error: { message: "Authentication required", type: "invalid_request_error" } },
{ status: 401 }
);
}
const forceRefresh = req.nextUrl.searchParams.get("refresh") === "true";
if (forceRefresh) {
const result = await refreshOpenRouterCatalog();
return NextResponse.json({
object: "list",
data: result.data,
meta: {
source: result.ok ? "fresh" : "error",
count: result.data.length,
error: result.error ?? undefined,
},
});
}
const result = await getOpenRouterCatalog();
return NextResponse.json({
object: "list",
data: result.data,
meta: {
source: result.fromCache ? (result.stale ? "stale-cache" : "cache") : "fresh",
cachedAt: result.cachedAt ?? undefined,
stale: result.stale,
count: result.data.length,
},
});
}

View File

@@ -74,6 +74,7 @@ export async function PUT(request: Request, { params }: { params: Promise<{ id:
rateLimitedUntil,
lastTested,
healthCheckInterval,
providerSpecificData: incomingPsd,
} = body;
const existing = await getProviderConnectionById(id);
@@ -98,6 +99,15 @@ export async function PUT(request: Request, { params }: { params: Promise<{ id:
if (lastTested !== undefined) updateData.lastTested = lastTested;
if (healthCheckInterval !== undefined) updateData.healthCheckInterval = healthCheckInterval;
// Merge providerSpecificData (partial update — preserve existing keys not sent by caller)
if (incomingPsd !== undefined && incomingPsd !== null && typeof incomingPsd === "object") {
const existingPsd =
existing.providerSpecificData && typeof existing.providerSpecificData === "object"
? existing.providerSpecificData
: {};
updateData.providerSpecificData = { ...existingPsd, ...incomingPsd };
}
const updated = await updateProviderConnection(id, updateData);
// Hide sensitive fields

View File

@@ -0,0 +1,174 @@
/**
* openrouterCatalog.ts — Feature 09
* Catálogo OpenRouter com cache persistente em arquivo JSON local.
*
* - TTL configurável via env OPENROUTER_CATALOG_TTL_MS (default: 24h)
* - Fallback stale-if-error: retorna último snapshot válido se fetch falhar
* - Atualização oportunista em background (não bloqueia o caller)
*/
import fs from "fs";
import path from "path";
const OPENROUTER_API_URL = "https://openrouter.ai/api/v1/models";
const DEFAULT_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
function getTTL(): number {
const env = process.env.OPENROUTER_CATALOG_TTL_MS;
return env ? parseInt(env, 10) : DEFAULT_TTL_MS;
}
function getCacheFilePath(): string {
const dataDir = process.env.DATA_DIR || path.join(process.cwd(), "data");
const cacheDir = path.join(dataDir, "cache");
if (!fs.existsSync(cacheDir)) {
fs.mkdirSync(cacheDir, { recursive: true });
}
return path.join(cacheDir, "openrouter-catalog.json");
}
interface CatalogEntry {
id: string;
name?: string;
description?: string;
context_length?: number;
pricing?: {
prompt?: string;
completion?: string;
image?: string;
request?: string;
};
top_provider?: {
max_completion_tokens?: number;
is_moderated?: boolean;
};
architecture?: {
modality?: string;
tokenizer?: string;
instruct_type?: string;
};
created?: number;
}
interface CacheFile {
fetchedAt: string;
data: CatalogEntry[];
}
/** Read cached catalog from disk. Returns null if not found or unparseable. */
function readCache(): CacheFile | null {
const filePath = getCacheFilePath();
try {
if (!fs.existsSync(filePath)) return null;
const raw = fs.readFileSync(filePath, "utf8");
return JSON.parse(raw) as CacheFile;
} catch {
return null;
}
}
/** Write catalog to disk cache. */
function writeCache(data: CatalogEntry[]): void {
const filePath = getCacheFilePath();
const cache: CacheFile = {
fetchedAt: new Date().toISOString(),
data,
};
try {
fs.writeFileSync(filePath, JSON.stringify(cache, null, 2), "utf8");
} catch (err) {
console.warn("[OpenRouterCatalog] Failed to write cache:", err);
}
}
/** Fetch fresh catalog from OpenRouter API. */
async function fetchFromAPI(): Promise<CatalogEntry[]> {
const res = await fetch(OPENROUTER_API_URL, {
headers: {
"User-Agent": "OmniRoute/2.0",
Accept: "application/json",
},
signal: AbortSignal.timeout(15_000),
});
if (!res.ok) {
throw new Error(`OpenRouter API returned ${res.status}: ${res.statusText}`);
}
const json = (await res.json()) as { data?: CatalogEntry[] };
const models = Array.isArray(json.data) ? json.data : [];
return models;
}
/**
* Get OpenRouter model catalog.
*
* Returns { data, stale, cachedAt } where:
* - data: list of models
* - stale: true if data is from a stale cache (fetch failed)
* - cachedAt: ISO string of when the data was cached (null if fresh fetch)
*/
export async function getOpenRouterCatalog(): Promise<{
data: CatalogEntry[];
stale: boolean;
cachedAt: string | null;
fromCache: boolean;
}> {
const ttl = getTTL();
const cache = readCache();
const now = Date.now();
// Return cached data if still within TTL
if (cache && cache.fetchedAt) {
const age = now - new Date(cache.fetchedAt).getTime();
if (age < ttl) {
return {
data: cache.data,
stale: false,
cachedAt: cache.fetchedAt,
fromCache: true,
};
}
}
// Cache expired or missing — attempt fresh fetch
try {
const data = await fetchFromAPI();
writeCache(data);
return { data, stale: false, cachedAt: null, fromCache: false };
} catch (err) {
console.warn("[OpenRouterCatalog] Fetch failed, using stale cache:", err);
// Stale-if-error: return old cache if available
if (cache) {
return {
data: cache.data,
stale: true,
cachedAt: cache.fetchedAt,
fromCache: true,
};
}
// No cache at all — return empty with error signal
return { data: [], stale: true, cachedAt: null, fromCache: false };
}
}
/**
* Force-refresh the catalog cache (ignores TTL).
* Used by admin endpoints and manual refresh actions.
*/
export async function refreshOpenRouterCatalog(): Promise<{
data: CatalogEntry[];
ok: boolean;
error?: string;
}> {
try {
const data = await fetchFromAPI();
writeCache(data);
return { data, ok: true };
} catch (err) {
const error = err instanceof Error ? err.message : String(err);
return { data: [], ok: false, error };
}
}

View File

@@ -769,6 +769,8 @@ export const updateProviderConnectionSchema = z
rateLimitedUntil: z.union([z.string(), z.null()]).optional(),
lastTested: z.union([z.string(), z.null()]).optional(),
healthCheckInterval: z.coerce.number().int().min(0).optional(),
// Partial patch of per-connection provider-specific settings (e.g. quota toggles)
providerSpecificData: z.record(z.string(), z.unknown()).optional(),
})
.superRefine((value, ctx) => {
if (Object.keys(value).length === 0) {