mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-02 21:32:10 +03:00
refactor(usage): type saveRequestUsage with UsageEntry interface + any-budget ratchet (#3512) (#6809)
Replace saveRequestUsage(entry: any) with a typed UsageEntry interface
mirroring the usage_history columns 1:1. Fields stay optional/nullable
since different writers (chatCore success/failure, rejected-request
accounting, Codex Responses WS) populate the row incrementally; tokens
stays unknown since callers pass either raw provider-shaped usage or
the normalized {input,output,cacheRead,...} shape.
Also cleaned the file's other any usages (getUsageHistory filter,
getUsageDb next-cursor cast, appendRequestLog tokens param,
getRecentLogs catch) so it now sits at zero any and can be added to
the check:any-budget:t11 zero-any allowlist.
Documents the DB-entity <-> TS-interface convention in
docs/architecture/CODEBASE_DOCUMENTATION.md Sec 11.
This commit is contained in:
committed by
GitHub
parent
6d3d122b84
commit
d96bd80343
@@ -80,6 +80,7 @@ _Living section — bullets land here as PRs merge into `release/v3.8.47` (paral
|
||||
- **chore(open-sse):** removed the vestigial `// @ts-nocheck` directive from `open-sse/utils/usageTracking.ts` ([#6173](https://github.com/diegosouzapw/OmniRoute/pull/6173)) — `tsc` under the standard `typecheck:core` gate reports 0 errors for this 595-line hot-path file (executed for every provider response), so the suppression was no longer needed; removing it restores type-checking on the token-usage extraction/normalization path. (thanks @KooshaPari)
|
||||
- **chore(cli):** the shell-completion cache paths (`readCache`/`refreshCache`/`writeCache`) in `bin/cli/commands/completion.mjs` no longer swallow errors into a bare `catch {}` ([#6257](https://github.com/diegosouzapw/OmniRoute/pull/6257)) — each now binds the error and, when the new `OMNIROUTE_DEBUG_COMPLETION` env var is set, emits a `[omniroute completion]` diagnostic to `stderr`; the caches still fail silently by default so a missing/corrupt cache never breaks tab-completion. (thanks @KooshaPari)
|
||||
- **chore(quality):** `validate-release-green --full-ci` reproduces the full `ci.yml` static gate set locally — the pre-flight now reads `ci.yml` itself and runs every `npm run check:*` from the `lint` / `quality-gate` / `quality-extended` / `docs-sync-strict` / `pr-test-policy` jobs (`--` ratchet flags preserved, `test-masking` against `GITHUB_BASE_REF=main`), skipping only the non-local `pr-evidence`/`codeql-ratchet`. Closes the gap where 11 static base-reds leaked to the v3.8.46 release PR in ~2h of layered CI. Also wired into `nightly-release-green` so a static base-red opens a tracking issue the night it lands. Regression guard: `tests/unit/validate-release-green.test.ts` (+5 `extractCiGates` cases).
|
||||
- **refactor(usage):** `saveRequestUsage(entry: any)` is now typed with a new `UsageEntry` interface mirroring the `usage_history` columns 1:1 (#3512) — the other stray `any`s in `src/lib/usage/usageHistory.ts` (`getUsageHistory` filter, the `getUsageDb` next-cursor cast, `appendRequestLog`'s legacy `tokens` param, `getRecentLogs`'s catch) were cleaned in the same pass, so the file now sits in the `check:any-budget:t11` zero-`any` allowlist. The DB-entity ↔ TS-interface convention is documented in `docs/architecture/CODEBASE_DOCUMENTATION.md` §11.
|
||||
|
||||
### 🙌 Contributors
|
||||
|
||||
|
||||
@@ -793,6 +793,20 @@ See [A2A-SERVER.md § Adding a New Skill](../frameworks/A2A-SERVER.md). Skills l
|
||||
inference for cross-module boundaries.
|
||||
- **Database**: never write raw SQL in routes or handlers — always go through
|
||||
`src/lib/db/` modules. Never add logic to `src/lib/localDb.ts`.
|
||||
- **DB-entity typing (#3512)**: a function that writes or reads a DB table's
|
||||
row shape should take/return a named TS interface mirroring that table's
|
||||
columns 1:1, not `any` or an inline anonymous type at the call site. Land
|
||||
the interface next to the function (e.g. `export interface UsageEntry` in
|
||||
`src/lib/usage/usageHistory.ts` above `saveRequestUsage`), keep individual
|
||||
fields optional/nullable when different writers populate the row
|
||||
incrementally, and prefer `unknown` over `any` for a field whose shape
|
||||
varies across callers (documented on the field, e.g. `UsageEntry.tokens`
|
||||
accepts both raw provider-shaped usage and the normalized shape). Once a
|
||||
file's `any` count reaches zero this way, add it to the
|
||||
`check:any-budget:t11` allowlist (`scripts/check/check-t11-any-budget.mjs`,
|
||||
`maxAny: 0`) so it can't regress. This is a first-slice convention — the
|
||||
broader "no anonymous `any`" cleanup is iterative across the rest of the
|
||||
codebase.
|
||||
- **Errors**: try/catch with specific error types, log with pino context. Never
|
||||
silently swallow errors in SSE streams; use abort signals for cleanup.
|
||||
- **Security**: never use `eval()` / `new Function()` / implied eval. Validate
|
||||
|
||||
@@ -22,6 +22,10 @@ const budget = [
|
||||
{ file: "src/lib/db/prompts.ts", maxAny: 0 },
|
||||
{ file: "src/lib/db/providers.ts", maxAny: 0 },
|
||||
{ file: "src/lib/db/settings.ts", maxAny: 0 },
|
||||
// #3512: saveRequestUsage typed with UsageEntry (DB-entity 1:1 interface); the
|
||||
// other any's in this file (getUsageHistory filter, nextCursor cast,
|
||||
// appendRequestLog tokens, getRecentLogs catch) were cleaned in the same pass.
|
||||
{ file: "src/lib/usage/usageHistory.ts", maxAny: 0 },
|
||||
{ file: "open-sse/config/providerRegistry.ts", maxAny: 0 },
|
||||
{ file: "open-sse/config/providerModels.ts", maxAny: 0 },
|
||||
{ file: "open-sse/mcp-server/audit.ts", maxAny: 0 },
|
||||
|
||||
@@ -553,17 +553,58 @@ export async function getUsageDb(sinceIso?: string | null, limit?: number, curso
|
||||
});
|
||||
|
||||
// Provide next cursor if we hit the limit (more rows exist)
|
||||
const nextCursor = rows.length === maxRows ? (rows[rows.length - 1] as any)?.timestamp : null;
|
||||
const nextCursor =
|
||||
rows.length === maxRows ? toStringOrNull(asRecord(rows[rows.length - 1]).timestamp) : null;
|
||||
|
||||
return { data: { history, nextCursor } };
|
||||
}
|
||||
|
||||
// ──────────────── Save Request Usage ────────────────
|
||||
|
||||
/**
|
||||
* DB-entity-mapped shape accepted by {@link saveRequestUsage}, mirroring the
|
||||
* `usage_history` table columns 1:1 (see `src/lib/db/migrations/`). Convention
|
||||
* (#3512): every `usage_history` writer should type its entry against this
|
||||
* interface instead of an inline anonymous object or `any` — call sites are
|
||||
* intentionally permissive (fields optional/nullable) because rows are built
|
||||
* incrementally across several extraction points (chatCore success/failure
|
||||
* paths, rejected-request accounting, the Codex Responses WS bridge).
|
||||
*
|
||||
* `tokens` stays `unknown` on purpose: callers pass either the raw
|
||||
* provider-shaped usage object (OpenAI `prompt_tokens`/`completion_tokens`,
|
||||
* Anthropic `input_tokens`/`cache_read_input_tokens`, …) or the already
|
||||
* normalized `{ input, output, cacheRead, cacheCreation, reasoning }` shape —
|
||||
* `getLoggedInputTokens`/`getLoggedOutputTokens`/`getPromptCache*Tokens` in
|
||||
* `./tokenAccounting` accept both and extract the right fields.
|
||||
*/
|
||||
export interface UsageEntry {
|
||||
provider?: string | null;
|
||||
model?: string | null;
|
||||
/** Raw or normalized token usage — see the interface doc above. */
|
||||
tokens?: unknown;
|
||||
status?: string | null;
|
||||
success?: boolean;
|
||||
latencyMs?: number;
|
||||
timeToFirstTokenMs?: number;
|
||||
errorCode?: string | null;
|
||||
/** ISO timestamp; defaults to `new Date().toISOString()` when omitted. */
|
||||
timestamp?: string;
|
||||
connectionId?: string | null;
|
||||
apiKeyId?: string | null;
|
||||
apiKeyName?: string | null;
|
||||
serviceTier?: string | null;
|
||||
/** @deprecated legacy snake_case fallback, read only if `serviceTier` is unset. */
|
||||
service_tier?: string | null;
|
||||
comboStrategy?: string | null;
|
||||
/** @deprecated legacy snake_case fallback, read only if `comboStrategy` is unset. */
|
||||
combo_strategy?: string | null;
|
||||
endpoint?: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save request usage entry to SQLite.
|
||||
*/
|
||||
export async function saveRequestUsage(entry: any) {
|
||||
export async function saveRequestUsage(entry: UsageEntry) {
|
||||
if (!shouldPersistToDisk) return;
|
||||
|
||||
try {
|
||||
@@ -666,10 +707,17 @@ export async function saveRequestUsage(entry: any) {
|
||||
|
||||
// ──────────────── Get Usage History ────────────────
|
||||
|
||||
export interface UsageHistoryFilter {
|
||||
provider?: string;
|
||||
model?: string;
|
||||
startDate?: string | number | Date;
|
||||
endDate?: string | number | Date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get usage history with optional filters.
|
||||
*/
|
||||
export async function getUsageHistory(filter: any = {}) {
|
||||
export async function getUsageHistory(filter: UsageHistoryFilter = {}) {
|
||||
const db = getDbInstance();
|
||||
let sql = "SELECT * FROM usage_history";
|
||||
const conditions: string[] = [];
|
||||
@@ -875,7 +923,7 @@ export async function appendRequestLog({
|
||||
model?: string;
|
||||
provider?: string;
|
||||
connectionId?: string;
|
||||
tokens?: any;
|
||||
tokens?: unknown;
|
||||
status?: string | number;
|
||||
}) {
|
||||
// Deprecated: request summaries now come from SQLite call_logs.
|
||||
@@ -909,8 +957,11 @@ export async function getRecentLogs(limit = 200) {
|
||||
const status = typeof row.status === "number" ? row.status : String(row.status || "-");
|
||||
return `${timestamp} | ${model} | ${provider} | ${account} | ${tokensIn} | ${tokensOut} | ${status}`;
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error("[usageDb] Failed to read recent call logs:", error.message);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"[usageDb] Failed to read recent call logs:",
|
||||
error instanceof Error ? error.message : String(error)
|
||||
);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user