mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-09 00:32:13 +03:00
fix(types,docs): clear the 5 typecheck errors and the fabricated env vars on the base
Third pass over the base-reds, from the 2026-08-06T22:51Z verdict on #9298 —
it reported "Typecheck (core)" with only the FIRST error; there are five, all on
the pure tip 9995bc4893. Two are real production defects.
**Real bugs**
- open-sse/services/compression/engines/ccr/index.ts:295 called
enforceGlobalBudget(entry.bytes) against an (owner, bytes) signature. The
`bytes` argument arrived undefined, so `ccrTotalBytes + undefined` is NaN,
`NaN > MAX` is false (the eviction loop exits immediately) and `NaN <= MAX` is
false (the re-admit is refused). The #9061 durable tier therefore NEVER
repopulated its in-memory map: every retrieve after a restart or an eviction
re-read from SQLite forever, and evictions could not prefer the owning
principal. Fixed and pinned by a new case in
tests/unit/ccr-durable-store-9061.test.ts (11/11) — verified failing against
the buggy call and passing against the fix.
- open-sse/services/combo/fusionPanel.ts:54 read `step.model` after #8894
widened ComboStep with ComboProviderWildcardStep (which carries modelPattern,
not model), so a wildcard step in a fusion panel pushed `undefined` onto the
panel. Now resolved through getComboModelString(), which already handles every
step shape and returns null for the ones without a concrete model id.
**Type-only**
- accountSemaphore.ts:203 — isBypassed() returns a plain boolean and cannot
narrow `number | null` (an `x is null | undefined` predicate would be unsound:
0 bypasses too). Added resolveActiveCap(), the narrowing companion isBypassed
is now defined in terms of; the acquire path uses the narrowed value.
- comboStructure.ts:140 — same #8894 widening: `prompt` only exists on a model
step, so it is now read under a kind check.
- firecrawlQuotaFetcher.ts:136 — the function returns full FirecrawlQuota
objects but was annotated Promise<QuotaInfo | null>, which made the
custom-base literal an excess-property error. Widened to the accurate type
(FirecrawlQuota extends QuotaInfo, so callers are unaffected).
**Fabricated docs (the "Docs sync + fabricated-docs (strict)" HARD failure)**
docs/ops/VM_DEPLOYMENT_GUIDE.md recommended OMNIROUTE_MAX_POOL_SIZE and
OMNIROUTE_DB_POOL_SIZE (#9471). Neither is read anywhere in the codebase.
Replaced with the two knobs that do exist and are already documented in
ENVIRONMENT.md: OMNIROUTE_MEMORY_MB and OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT.
typecheck:core 5 errors -> 0. check:fabricated-docs + check:env-doc-sync OK.
accountSemaphore 6/6, ccr-durable-store 11/11, ccr-protocol 9/9,
combo-fusion-strategy 10/10, combo-fusion-comboref 5/5, combo-fusion-warn 4/4,
firecrawl-executor 7/7, executor-firecrawl-fetch 4/4.
Refs #9298
This commit is contained in:
@@ -429,6 +429,7 @@ For deployments on small VPS instances (1 GB RAM or less):
|
||||
|
||||
- **Disable background services** — set `OMNIROUTE_DISABLE_BACKGROUND_SERVICES=1` to skip scheduler, MCP server, and periodic maintenance tasks. See `docs/reference/ENVIRONMENT.md`.
|
||||
- **Use SQLite WAL mode** — enabled by default, reduces peak memory during concurrent reads.
|
||||
- **Limit connection concurrency** — reduce `OMNIROUTE_MAX_POOL_SIZE` and `OMNIROUTE_DB_POOL_SIZE` in your environment.
|
||||
- **Cap the V8 heap** — set `OMNIROUTE_MEMORY_MB` (e.g. `512`) so the runtime does not calibrate a ceiling larger than the VM. See `docs/reference/ENVIRONMENT.md`.
|
||||
- **Limit concurrent heavy requests** — lower `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (default `1`); excess requests get a retryable `503` with `Retry-After` instead of competing for memory.
|
||||
- **Avoid `next build` on the VPS** — build locally and deploy the standalone output (`.next/standalone/`).
|
||||
- **Monitor with `top` / `free -m`** — OmniRoute typically uses 200-400 MB RSS at idle on a 1 GB VM.
|
||||
|
||||
@@ -54,8 +54,21 @@ export function buildAccountSemaphoreKey({
|
||||
return `${String(provider)}:${String(accountKey)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Effective positive cap, or null when the semaphore is bypassed (unset/<=0).
|
||||
*
|
||||
* Narrowing companion of {@link isBypassed}: that one returns a plain boolean, so
|
||||
* TypeScript cannot narrow `number | null` to `number` in its else-branch (a
|
||||
* `x is null | undefined` predicate would be unsound — 0 bypasses too). Callers
|
||||
* that need the VALUE after the guard go through here instead of casting.
|
||||
*/
|
||||
function resolveActiveCap(maxConcurrency?: number | null): number | null {
|
||||
if (maxConcurrency == null || maxConcurrency <= 0) return null;
|
||||
return maxConcurrency;
|
||||
}
|
||||
|
||||
function isBypassed(maxConcurrency?: number | null): boolean {
|
||||
return maxConcurrency == null || maxConcurrency <= 0;
|
||||
return resolveActiveCap(maxConcurrency) === null;
|
||||
}
|
||||
|
||||
function createNoopReleaseFn(): () => void {
|
||||
@@ -192,7 +205,8 @@ export function acquire(
|
||||
maxQueueSize = DEFAULT_MAX_QUEUE_SIZE,
|
||||
}: AcquireAccountSemaphoreOptions = {}
|
||||
): Promise<() => void> {
|
||||
if (isBypassed(maxConcurrency)) {
|
||||
const activeCap = resolveActiveCap(maxConcurrency);
|
||||
if (activeCap === null) {
|
||||
return Promise.resolve(createNoopReleaseFn());
|
||||
}
|
||||
|
||||
@@ -200,7 +214,7 @@ export function acquire(
|
||||
return Promise.reject(makeAbortError(signal));
|
||||
}
|
||||
|
||||
const gate = ensureGate(semaphoreKey, maxConcurrency);
|
||||
const gate = ensureGate(semaphoreKey, activeCap);
|
||||
clearCleanupTimer(gate);
|
||||
|
||||
if (gate.running < gate.maxConcurrency && !isBlocked(gate)) {
|
||||
|
||||
@@ -137,7 +137,9 @@ function normalizeRuntimeStep(
|
||||
: {}),
|
||||
weight,
|
||||
label,
|
||||
prompt: step.prompt || null,
|
||||
// `prompt` is a per-step pipeline input and only exists on a model step —
|
||||
// #8894 widened the union with ComboProviderWildcardStep, which has no prompt.
|
||||
prompt: (step.kind === "model" ? step.prompt : null) || null,
|
||||
} satisfies ResolvedComboTarget;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* literal `auto/*` string panel member already behaves via the single-
|
||||
* dispatch safety net in src/sse/handlers/chat.ts.
|
||||
*/
|
||||
import { normalizeComboStep } from "../../../src/lib/combos/steps.ts";
|
||||
import { getComboModelString, normalizeComboStep } from "../../../src/lib/combos/steps.ts";
|
||||
import { executeComboRefUnit } from "./runtimeUnits.ts";
|
||||
import type {
|
||||
ComboCollectionLike,
|
||||
@@ -51,7 +51,11 @@ export function extractFusionPanelSpec(
|
||||
panel.push(step.comboName);
|
||||
return;
|
||||
}
|
||||
panel.push(step.model);
|
||||
// #8894 widened ComboStep with ComboProviderWildcardStep, which carries a
|
||||
// modelPattern instead of a model. getComboModelString() already resolves any
|
||||
// step shape (and returns null for the ones with no concrete model id).
|
||||
const modelStr = getComboModelString(step);
|
||||
if (modelStr) panel.push(modelStr);
|
||||
});
|
||||
return { panel, comboRefUnits };
|
||||
}
|
||||
|
||||
@@ -292,7 +292,8 @@ function rehydrateEntry(hash: string, principalId: string, now: number): CcrEntr
|
||||
|
||||
// Re-admit through the same budgets a fresh store would face. If the block no longer
|
||||
// fits, it stays on disk and is served straight from the row instead of being cached.
|
||||
if (enforcePrincipalBudget(entry.principalId, entry.bytes) && enforceGlobalBudget(entry.bytes)) {
|
||||
const { principalId: owner, bytes } = entry;
|
||||
if (enforcePrincipalBudget(owner, bytes) && enforceGlobalBudget(owner, bytes)) {
|
||||
const key = buildStoreKey(hash, principalId === ANON ? undefined : principalId);
|
||||
ccrStore.set(key, entry);
|
||||
ccrTotalBytes += entry.bytes;
|
||||
|
||||
@@ -110,7 +110,8 @@ export function getFirecrawlBaseUrl(connection?: Record<string, unknown>): strin
|
||||
return envBase.replace(/\/+$/, "");
|
||||
}
|
||||
const providerData = toRecord(connection?.providerSpecificData);
|
||||
const connBase = typeof connection?.baseUrl === "string" ? connection.baseUrl : providerData?.baseUrl;
|
||||
const connBase =
|
||||
typeof connection?.baseUrl === "string" ? connection.baseUrl : providerData?.baseUrl;
|
||||
if (typeof connBase === "string" && connBase.trim() && !connBase.includes("api.firecrawl.dev")) {
|
||||
return connBase.trim().replace(/\/+$/, "");
|
||||
}
|
||||
@@ -120,7 +121,11 @@ export function getFirecrawlBaseUrl(connection?: Record<string, unknown>): strin
|
||||
export async function fetchFirecrawlQuota(
|
||||
connectionId: string,
|
||||
connection?: Record<string, unknown>
|
||||
): Promise<QuotaInfo | null> {
|
||||
// FirecrawlQuota, not the base QuotaInfo: every return here is a full credit
|
||||
// breakdown (remainingCredits / planCredits / extraCreditsInferred / overPlan),
|
||||
// and the narrower annotation made the custom-base literal below an excess-
|
||||
// property error. FirecrawlQuota extends QuotaInfo, so callers are unaffected.
|
||||
): Promise<FirecrawlQuota | null> {
|
||||
const cached = quotaCache.get(connectionId);
|
||||
if (cached && Date.now() - cached.fetchedAt < CACHE_TTL_MS) {
|
||||
return cached.quota;
|
||||
|
||||
@@ -119,6 +119,32 @@ describe("CCR engine survives losing its in-memory map (#9061)", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("re-admits the disk row into the fresh map (the enforceGlobalBudget arity bug)", async () => {
|
||||
// The restart path re-admits a disk-served block through the same budgets a fresh
|
||||
// store would face. #9061 called enforceGlobalBudget(entry.bytes) with ONE argument
|
||||
// against a (owner, bytes) signature: `bytes` arrived undefined, `ccrTotalBytes +
|
||||
// undefined` is NaN, and `NaN <= MAX` is false — so the re-admit never happened and
|
||||
// the map stayed empty, re-reading from disk on every single retrieve. Typecheck
|
||||
// caught the arity; this pins the observable behaviour.
|
||||
const text = "z".repeat(2_000);
|
||||
const stored = ccr.tryStoreBlock(text, "principal-readmit");
|
||||
assert.equal(stored.stored, true);
|
||||
await ccr.flushCcrDurableWrites();
|
||||
|
||||
const restarted = await import(`${ccrPath}?restart=9061-readmit`);
|
||||
assert.equal(
|
||||
restarted.getCcrStoreStats("principal-readmit").entries,
|
||||
0,
|
||||
"a fresh instance starts with an empty map"
|
||||
);
|
||||
assert.equal(restarted.retrieveBlock(stored.hash, "principal-readmit"), text);
|
||||
assert.equal(
|
||||
restarted.getCcrStoreStats("principal-readmit").entries,
|
||||
1,
|
||||
"the disk-served block must be re-admitted into the map, not re-read every time"
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps the principal boundary across the restart", async () => {
|
||||
const text = "y".repeat(2_000);
|
||||
const stored = ccr.tryStoreBlock(text, "principal-a");
|
||||
|
||||
Reference in New Issue
Block a user