mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-15 11:43:10 +03:00
fix(sse): exclude search providers from credential-health scheduler sweep
The credential-health scheduler's sweep() tested every active connection every 5 minutes with no exclusion for search providers. For providers in SEARCH_VALIDATOR_CONFIGS (tavily-search, exa-search, serper-search, brave-search, google-pse-search, linkup-search, searchapi-search, youcom-search), "validation" fires a real billed upstream query (e.g. POST api.tavily.com/search), so the periodic sweep silently burned quota with no user-initiated search. Exclude connections whose provider id is registered in SEARCH_VALIDATOR_CONFIGS from the sweep's connection-selection filter. Non-search API-key/OAuth connections remain monitored (#9180, #9289 regressions verified green). Closes #9970
This commit is contained in:
@@ -0,0 +1 @@
|
||||
- fix(sse): exclude search providers from credential-health scheduler sweep to stop burning billed API queries (#9970)
|
||||
@@ -146,7 +146,7 @@ OmniRoute uses **SQLite** (via `better-sqlite3`) for all persistence. These vari
|
||||
| `OMNIROUTE_SKIP_DB_HEALTHCHECK` | _(unset)_ | `src/lib/db/core.ts` / `src/lib/db/healthCheck.ts` | Set to `1` to skip the SQLite integrity health check on startup. Useful for faster boot on large databases. |
|
||||
| `CREDENTIAL_HEALTH_CHECK_INTERVAL` | `300000` | `open-sse/config/constants.ts` / `src/lib/credentialHealth/scheduler.ts` | Interval (ms) for the background credential health check scheduler. Minimum: 10000 (10s). |
|
||||
| `CREDENTIAL_HEALTH_CACHE_TTL` | `300000` | `open-sse/config/constants.ts` / `src/lib/credentialHealth/cache.ts` | TTL (ms) for cached credential health status. |
|
||||
| `OMNIROUTE_DISABLE_CREDENTIAL_HEALTH_CHECK` | `false` | `src/lib/credentialHealth/scheduler.ts` | Set to `1` or `true` to disable background periodic testing of provider connections. |
|
||||
| `OMNIROUTE_DISABLE_CREDENTIAL_HEALTH_CHECK` | `false` | `src/lib/credentialHealth/scheduler.ts` | Set to `1` or `true` to disable background periodic testing of provider connections. Search providers (`SEARCH_VALIDATOR_CONFIGS` in `src/lib/providers/validation/searchProviders.ts`, e.g. `tavily-search`) are always excluded from the sweep — their "validation" is a real billed upstream query, so they are never health-checked on a timer (#9970). |
|
||||
| `HOST` | `0.0.0.0` | `scripts/dev/run-next.mjs` | Bind address for the Next.js dev/start server. Overrides the default `0.0.0.0` when set. |
|
||||
| `HOSTNAME` | `127.0.0.1` | `scripts/dev/run-next-playwright.mjs` | Bind address used by the Playwright runner when launching Next.js. Defaults to `127.0.0.1` for hermetic tests. **Do not use for `omniroute serve`** — use `OMNIROUTE_SERVER_HOST` instead (POSIX shells auto-set `HOSTNAME` to the machine name; `.env` cannot override it). |
|
||||
| `OMNIROUTE_SERVER_HOST` | `0.0.0.0` | `bin/cli/commands/serve.mjs` | Bind address for `omniroute serve`. Avoids collision with the POSIX shell `HOSTNAME` variable (always set to the machine name by bash/zsh). Falls back to `0.0.0.0` when unset. (#6194) |
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
} from "@/lib/credentialHealth/cache";
|
||||
import { emit } from "@/lib/events/eventBus";
|
||||
import { isAutomatedTestProcess } from "@/shared/utils/testProcess";
|
||||
import { SEARCH_VALIDATOR_CONFIGS } from "@/lib/providers/validation/searchProviders";
|
||||
|
||||
// ── Config ────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -230,7 +231,13 @@ export async function sweep(): Promise<void> {
|
||||
try {
|
||||
const raw = await getProviderConnections({ isActive: true });
|
||||
connections = (Array.isArray(raw) ? raw : []).filter(
|
||||
(conn: any) => conn && conn.id && (conn.authType === "apikey" || conn.authType === "oauth")
|
||||
(conn: any) =>
|
||||
conn &&
|
||||
conn.id &&
|
||||
(conn.authType === "apikey" || conn.authType === "oauth") &&
|
||||
// #9970: search-provider "validation" fires a REAL billed upstream
|
||||
// query (e.g. POST api.tavily.com/search) — never sweep these.
|
||||
!(conn.provider in SEARCH_VALIDATOR_CONFIGS)
|
||||
) as Array<{
|
||||
id: string;
|
||||
provider: string;
|
||||
|
||||
96
tests/unit/credential-health-search-providers.test.ts
Normal file
96
tests/unit/credential-health-search-providers.test.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* Regression test for #9970 — credential-health scheduler burns real billed
|
||||
* API queries for search providers.
|
||||
*
|
||||
* Search-provider "validation" (SEARCH_VALIDATOR_CONFIGS, e.g. tavily-search)
|
||||
* issues a real upstream query (POST api.tavily.com/search) rather than a
|
||||
* cheap auth probe. The scheduler's periodic sweep() must exclude connections
|
||||
* whose provider id is registered in SEARCH_VALIDATOR_CONFIGS so it never
|
||||
* fires a billed query on a timer.
|
||||
*
|
||||
* Mirrors the source-inspection style of
|
||||
* tests/unit/credential-health-active-connections-9180.test.ts.
|
||||
*/
|
||||
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import fs from "node:fs";
|
||||
|
||||
const schedulerSource = fs.readFileSync(
|
||||
new URL("../../src/lib/credentialHealth/scheduler.ts", import.meta.url),
|
||||
"utf8"
|
||||
);
|
||||
|
||||
const searchProvidersSource = fs.readFileSync(
|
||||
new URL("../../src/lib/providers/validation/searchProviders.ts", import.meta.url),
|
||||
"utf8"
|
||||
);
|
||||
|
||||
function getSweepConnectionSelection(): string {
|
||||
const start = schedulerSource.indexOf("export async function sweep(): Promise<void>");
|
||||
assert.notEqual(start, -1, "credential-health sweep must exist");
|
||||
|
||||
const end = schedulerSource.indexOf("\n if (connections.length === 0) return;", start);
|
||||
assert.notEqual(end, -1, "credential-health connection-selection block must exist");
|
||||
|
||||
return schedulerSource.slice(start, end);
|
||||
}
|
||||
|
||||
test("#9970 scheduler imports SEARCH_VALIDATOR_CONFIGS to classify billed-query providers", () => {
|
||||
assert.match(
|
||||
schedulerSource,
|
||||
/import\s*\{\s*SEARCH_VALIDATOR_CONFIGS\s*\}\s*from\s*"@\/lib\/providers\/validation\/searchProviders"/,
|
||||
"scheduler.ts must import SEARCH_VALIDATOR_CONFIGS from the search-provider validators module"
|
||||
);
|
||||
});
|
||||
|
||||
test("#9970 sweep() excludes search providers from the connection-selection filter", () => {
|
||||
const selection = getSweepConnectionSelection();
|
||||
|
||||
assert.match(
|
||||
selection,
|
||||
/SEARCH_VALIDATOR_CONFIGS/,
|
||||
"sweep()'s connection-selection block must reference SEARCH_VALIDATOR_CONFIGS to exclude search providers"
|
||||
);
|
||||
|
||||
assert.match(
|
||||
selection,
|
||||
/!\(conn\.provider in SEARCH_VALIDATOR_CONFIGS\)/,
|
||||
"sweep() must filter out connections whose provider id is a registered search-validator provider"
|
||||
);
|
||||
});
|
||||
|
||||
test("#9970 sweep() still keeps API-key + OAuth eligibility intact (no regression on #9180)", () => {
|
||||
const selection = getSweepConnectionSelection();
|
||||
|
||||
assert.match(
|
||||
selection,
|
||||
/getProviderConnections\(\{\s*isActive:\s*true\s*\}\)/,
|
||||
"the scheduler must still request only active provider connections"
|
||||
);
|
||||
|
||||
assert.match(
|
||||
selection,
|
||||
/conn\.authType === "apikey"/,
|
||||
"API-key connections must remain eligible"
|
||||
);
|
||||
|
||||
assert.match(selection, /conn\.authType === "oauth"/, "OAuth connections must remain eligible");
|
||||
});
|
||||
|
||||
test("#9970 trust anchor: SEARCH_VALIDATOR_CONFIGS providers target real billed upstream endpoints", () => {
|
||||
// Sanity-check the assumption driving the fix: the search validators really
|
||||
// do fire live upstream queries (not just an auth ping), so excluding them
|
||||
// from the periodic sweep is the correct trade-off.
|
||||
assert.match(
|
||||
searchProvidersSource,
|
||||
/api\.tavily\.com\/search/,
|
||||
"tavily-search validator must target the real Tavily search endpoint"
|
||||
);
|
||||
|
||||
assert.match(
|
||||
searchProvidersSource,
|
||||
/export const SEARCH_VALIDATOR_CONFIGS/,
|
||||
"SEARCH_VALIDATOR_CONFIGS must be exported so the scheduler can reference it"
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user